Square Payment Integration for Web Applications
Comprehensive Square Web Payments SDK integration covering card tokenization, Apple Pay, Google Pay, ACH payments, and omnichannel payment strategies for retail and e-commerce.
Table of Contents
- When to Choose Square
- Web Payments SDK Setup
- Server-Side Integration
- React Component Implementation
- Digital Wallets Integration
- Webhook Configuration
- Omnichannel Considerations
When to Choose Square
Square is the right choice when:
- Omnichannel retail - You need unified online and in-person payments
- Small to medium business - Transparent pricing, no monthly fees
- Quick setup - Faster time-to-market than enterprise solutions
- US/Canada/UK/Australia focus - Limited international coverage
I've used Square for retail clients who needed both e-commerce and POS integration without managing multiple payment providers.
Web Payments SDK Setup
Configuration
// config/square.ts import { Client, Environment } from 'square'; export const squareClient = new Client({ accessToken: process.env.SQUARE_ACCESS_TOKEN!, environment: process.env.NODE_ENV === 'production' ? Environment.Production : Environment.Sandbox, }); export const SQUARE_CONFIG = { applicationId: process.env.SQUARE_APPLICATION_ID!, locationId: process.env.SQUARE_LOCATION_ID!, environment: process.env.NODE_ENV === 'production' ? 'production' : 'sandbox', };
Server-Side Integration
Payment Processing
// services/square/PaymentService.ts import { squareClient, SQUARE_CONFIG } from '@/config/square'; import { randomUUID } from 'crypto'; interface CreatePaymentParams { sourceId: string; // Token from Web Payments SDK orderId: string; amount: number; // In cents currency: string; customerId?: string; note?: string; } export class SquarePaymentService { async createPayment(params: CreatePaymentParams): Promise<{ paymentId: string; status: string; receiptUrl?: string; }> { const { sourceId, orderId, amount, currency, customerId, note } = params; // Validate order const order = await this.orderRepo.findById(orderId); if (!order || order.totalAmountCents !== amount) { throw new PaymentError('INVALID_ORDER', 'Order validation failed'); } try { const { result } = await squareClient.paymentsApi.createPayment({ sourceId, idempotencyKey: `${orderId}-${Date.now()}`, amountMoney: { amount: BigInt(amount), currency, }, locationId: SQUARE_CONFIG.locationId, referenceId: orderId, customerId, note: note || `Order #${order.orderNumber}`, autocomplete: true, // Capture immediately }); const payment = result.payment!; // Store payment record await this.paymentRepo.create({ orderId, squarePaymentId: payment.id!, amount, currency, status: payment.status!, receiptUrl: payment.receiptUrl, }); if (payment.status === 'COMPLETED') { await this.orderService.confirmOrder(orderId); } return { paymentId: payment.id!, status: payment.status!, receiptUrl: payment.receiptUrl, }; } catch (error: any) { console.error('Square payment failed:', error); if (error.errors) { const squareError = error.errors[0]; throw new PaymentError( squareError.code, squareError.detail || 'Payment failed' ); } throw new PaymentError('PAYMENT_ERROR', 'Payment processing failed'); } } async refundPayment( paymentId: string, amount: number, currency: string, reason?: string ): Promise<{ refundId: string; status: string }> { const { result } = await squareClient.refundsApi.refundPayment({ paymentId, idempotencyKey: randomUUID(), amountMoney: { amount: BigInt(amount), currency, }, reason, }); return { refundId: result.refund!.id!, status: result.refund!.status!, }; } }
React Component Implementation
// components/payment/SquarePayment.tsx import { useEffect, useState, useCallback } from 'react'; interface SquarePaymentProps { applicationId: string; locationId: string; orderId: string; amount: number; currency: string; onSuccess: (result: any) => void; onError: (error: any) => void; } export function SquarePayment({ applicationId, locationId, orderId, amount, currency, onSuccess, onError, }: SquarePaymentProps) { const [card, setCard] = useState<any>(null); const [isLoading, setIsLoading] = useState(true); const [isProcessing, setIsProcessing] = useState(false); useEffect(() => { let payments: any; async function initializeSquare() { try { // Load Square Web Payments SDK if (!window.Square) { await loadSquareScript(); } payments = window.Square.payments(applicationId, locationId); // Initialize card payment method const cardInstance = await payments.card(); await cardInstance.attach('#card-container'); setCard(cardInstance); setIsLoading(false); } catch (error) { console.error('Square initialization failed:', error); onError(error); setIsLoading(false); } } initializeSquare(); return () => { card?.destroy(); }; }, [applicationId, locationId]); const handlePayment = useCallback(async () => { if (!card || isProcessing) return; setIsProcessing(true); try { // Tokenize the card const tokenResult = await card.tokenize(); if (tokenResult.status !== 'OK') { throw new Error(tokenResult.errors?.[0]?.message || 'Tokenization failed'); } // Send token to server const response = await fetch('/api/square/create-payment', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ sourceId: tokenResult.token, orderId, amount, currency, }), }); const result = await response.json(); if (!response.ok) { throw new Error(result.message || 'Payment failed'); } onSuccess(result); } catch (error) { onError(error); } finally { setIsProcessing(false); } }, [card, isProcessing, orderId, amount, currency, onSuccess, onError]); if (isLoading) { return <div className="animate-pulse h-48 bg-gray-100 rounded-lg" />; } return ( <div className="space-y-4"> <div id="card-container" className="border rounded-lg p-4 bg-white min-h-[100px]" /> <button onClick={handlePayment} disabled={isProcessing} className="w-full py-3 px-4 bg-black text-white font-semibold rounded-lg hover:bg-gray-800 disabled:bg-gray-400 disabled:cursor-not-allowed transition-colors" > {isProcessing ? 'Processing...' : `Pay $${(amount / 100).toFixed(2)}`} </button> </div> ); } function loadSquareScript(): Promise<void> { return new Promise((resolve, reject) => { const script = document.createElement('script'); script.src = 'https://sandbox.web.squarecdn.com/v1/square.js'; script.onload = () => resolve(); script.onerror = () => reject(new Error('Failed to load Square SDK')); document.head.appendChild(script); }); }
Digital Wallets Integration
// Adding Apple Pay and Google Pay async function initializeDigitalWallets(payments: any) { // Apple Pay const applePay = await payments.applePay({ countryCode: 'US', currencyCode: 'USD', total: { label: 'Your Company', amount: '10.00', }, }); if (await applePay.available()) { await applePay.attach('#apple-pay-button'); } // Google Pay const googlePay = await payments.googlePay({ countryCode: 'US', currencyCode: 'USD', totalPriceLabel: 'Total', totalPrice: '10.00', }); if (await googlePay.available()) { await googlePay.attach('#google-pay-button'); } return { applePay, googlePay }; }
Webhook Configuration
// services/square/WebhookService.ts import crypto from 'crypto'; export class SquareWebhookService { verifySignature( body: string, signature: string, webhookSignatureKey: string, notificationUrl: string ): boolean { const combined = notificationUrl + body; const expectedSignature = crypto .createHmac('sha256', webhookSignatureKey) .update(combined) .digest('base64'); return signature === expectedSignature; } async handleWebhook(event: any): Promise<void> { const { type, data } = event; switch (type) { case 'payment.completed': await this.handlePaymentCompleted(data.object.payment); break; case 'payment.updated': await this.handlePaymentUpdated(data.object.payment); break; case 'refund.created': await this.handleRefundCreated(data.object.refund); break; case 'dispute.created': await this.handleDisputeCreated(data.object.dispute); break; } } }
Omnichannel Considerations
Square's strength is unified commerce:
// Linking online and in-person customers async function linkCustomerAcrossChannels( email: string, onlineCustomerId?: string ): Promise<string> { // Search for existing Square customer const { result } = await squareClient.customersApi.searchCustomers({ query: { filter: { emailAddress: { exact: email }, }, }, }); if (result.customers && result.customers.length > 0) { const squareCustomer = result.customers[0]; // Link to your system if (onlineCustomerId) { await customerRepo.update(onlineCustomerId, { squareCustomerId: squareCustomer.id, }); } return squareCustomer.id!; } // Create new Square customer const { result: newCustomer } = await squareClient.customersApi.createCustomer({ emailAddress: email, referenceId: onlineCustomerId, }); return newCustomer.customer!.id!; }
Conclusion
Square excels for:
- Omnichannel retail - Unified online and POS
- Quick integration - Simple API, good documentation
- Transparent pricing - No monthly fees, flat rate
- US-focused businesses - Limited international support
Consider Stripe or Adyen for international expansion; choose Square for unified North American retail.
Related Articles
Payment Integrations28 min read
Stripe Payment Integration: Production Patterns for React and Node.js
Production Stripe integration with Payment Intents, webhooks, and 3D Secure. Covers subscription billing, error handling, and PCI compliance patterns.
Payment Integrations24 min read
PayPal Payment Integration: Production Implementation Guide
Integrate PayPal with Orders API and Smart Buttons. Covers webhook setup, subscription payments, and real error handling patterns from production.
Security Engineering18 min read
API Security Hardening: A Practitioner's Guide
Secure your APIs with rate limiting, input validation, and CORS configuration. Production-tested checklist covering authentication, encryption, and error handling.