Payment Integrations

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.

Khalid Aboubakr
20 min read
SquarePayment GatewayReactNodejsWeb Payments SdkIn PersonOmnichannel

Table of Contents

  1. When to Choose Square
  2. Web Payments SDK Setup
  3. Server-Side Integration
  4. React Component Implementation
  5. Digital Wallets Integration
  6. Webhook Configuration
  7. 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:

  1. Omnichannel retail - Unified online and POS
  2. Quick integration - Simple API, good documentation
  3. Transparent pricing - No monthly fees, flat rate
  4. US-focused businesses - Limited international support

Consider Stripe or Adyen for international expansion; choose Square for unified North American retail.

Related Articles

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.