skills/integrations-apis/pos-integration/SKILL.md
Connect your physical point-of-sale system to your online store for unified inventory, shared customer records, and omnichannel order management
npx skillsauth add finsilabs/awesome-ecommerce-skills pos-integrationInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
Point-of-sale integration connects your physical retail operations with your online store, enabling unified inventory visibility, centralized order management, and consistent customer profiles across channels. When a customer buys in-store, the online store must reflect the updated inventory immediately; when they return an online order in-store, the refund must flow back to the payment gateway. This skill covers connecting Square and Shopify POS to your commerce platform, synchronizing inventory in real time, and handling cross-channel returns.
| Platform | Easiest POS Integration | What Gets Unified | |----------|------------------------|------------------| | Shopify | Shopify POS (built-in, $89/month for Pro) — designed to work with Shopify's online store natively | Inventory syncs instantly between POS and online store; orders appear in unified admin; customer profiles and loyalty points unified | | WooCommerce | Square + WooCommerce Square plugin (free) | Inventory syncs bidirectionally; Square POS sales update WooCommerce stock; orders can be imported into WooCommerce | | BigCommerce | Square + BigCommerce Square integration | Same as WooCommerce but configured in BigCommerce's channel settings; BigCommerce also supports Stripe Terminal directly | | Custom / Headless | Square API or Stripe Terminal | Full control — sync inventory via webhooks, import POS orders via API, build unified order dashboard |
Shopify POS is the most seamless option for Shopify stores since inventory is managed in a single system:
Subscribe to Shopify POS Pro ($89/month or included in Shopify Plus):
Set up locations for each store:
Inventory automatically stays in sync:
Configure click-and-collect (Buy Online, Pick Up In Store):
Connect Square to WooCommerce:
How inventory sync works:
Handle in-store returns of online orders:
Initialize the Square client:
// lib/pos/square-client.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 { catalogApi, inventoryApi, ordersApi, paymentsApi, locationsApi } = squareClient;
Handle Square inventory webhooks (in-store sale → update online store):
// POST /api/webhooks/square
import { createHmac } from 'node:crypto';
export async function POST(req: NextRequest) {
const rawBody = Buffer.from(await req.arrayBuffer());
const signature = req.headers.get('x-square-hmacsha256-signature') ?? '';
// Square HMAC includes the full notification URL in the signature
const notificationUrl = `${process.env.APP_URL}/api/webhooks/square`;
const expected = createHmac('sha256', process.env.SQUARE_WEBHOOK_SECRET!)
.update(notificationUrl + rawBody.toString('utf8'))
.digest('base64');
if (signature !== expected) {
return NextResponse.json({ error: 'Invalid signature' }, { status: 401 });
}
const event = JSON.parse(rawBody.toString('utf8'));
if (event.type === 'inventory.count.updated') {
const counts = event.data.object.inventory_counts ?? [];
for (const count of counts) {
const variant = await db.variants.findBySquareCatalogId(count.catalog_object_id);
if (!variant) continue;
await db.inventory.updateLocationQuantity(variant.sku, count.location_id, parseInt(count.quantity));
const total = await db.inventory.getTotalAvailable(variant.sku);
await redis.setex(`inventory:${variant.productId}`, 3600, String(total));
}
}
return NextResponse.json({ accepted: true });
}
Handle cross-channel returns (online order returned in-store):
// lib/pos/returns.ts
export async function processInStoreReturn(params: {
orderId: string;
lineItems: Array<{ lineItemId: string; quantity: number }>;
locationId: string;
}) {
const { orderId, lineItems, locationId } = params;
const order = await db.orders.findById(orderId);
if (!order) throw new Error(`Order ${orderId} not found`);
// Calculate refund amount
const refundAmount = lineItems.reduce((sum, item) => {
const line = order.lineItems.find(l => l.id === item.lineItemId)!;
return sum + (line.unitPriceCents * item.quantity);
}, 0);
// Issue refund via original payment gateway
if (order.paymentGateway === 'stripe') {
await stripe.refunds.create({
payment_intent: order.stripePaymentIntentId,
amount: refundAmount,
metadata: { orderId, locationId, channel: 'in_store_return' },
});
} else if (order.paymentGateway === 'square') {
await paymentsApi.createPaymentRefund({
idempotencyKey: `return_${orderId}_${Date.now()}`,
paymentId: order.squarePaymentId!,
amountMoney: { amount: BigInt(refundAmount), currency: 'USD' },
});
}
// Restock returned items at the return location
for (const item of lineItems) {
const line = order.lineItems.find(l => l.id === item.lineItemId)!;
await db.inventory.incrementLocationQuantity(line.sku, locationId, item.quantity);
}
await db.orders.addReturn({ orderId, lineItems, processedAtLocation: locationId, processedAt: new Date() });
}
Sync your product catalog to Square (required before Square can track inventory):
export async function syncProductToSquare(product: Product) {
const { result } = await catalogApi.batchUpsertCatalogObjects({
idempotencyKey: `sync_${product.sku}_${Date.now()}`,
batches: [{
objects: [{
type: 'ITEM',
id: `#item_${product.sku}`,
itemData: {
name: product.name,
variations: product.variants.map(variant => ({
type: 'ITEM_VARIATION' as const,
id: `#var_${variant.sku}`,
itemVariationData: {
itemId: `#item_${product.sku}`,
name: variant.name,
sku: variant.sku,
pricingType: 'FIXED_PRICING',
priceMoney: { amount: BigInt(Math.round(variant.price * 100)), currency: 'USD' },
trackInventory: true,
},
})),
},
}],
}],
});
// Store Square-assigned IDs for future inventory updates
for (const mapping of result.idMappings ?? []) {
if (mapping.clientObjectId?.startsWith('#var_')) {
const sku = mapping.clientObjectId.replace('#var_', '');
await db.variants.update(sku, { squareCatalogVariationId: mapping.objectId });
}
}
}
| Problem | Solution |
|---------|----------|
| WooCommerce Square sync stops working | Re-authenticate the Square connection in WooCommerce → Settings → Integrations → Square — OAuth tokens expire; the plugin usually prompts for re-auth |
| Inventory oversell due to sync lag | Set safety stock buffer in your marketplace/POS settings; for high-demand items, do a final inventory check at checkout against Square's inventory API |
| Square catalog IDs diverge from your SKUs | Maintain a mapping table between your internal SKU and Square's catalog variation IDs; rebuild it from Square's catalog if it gets out of sync |
| Cross-channel return creates duplicate inventory | Only restock inventory when the return is physically received in-store (status: received), not when the return is initiated |
| Shopify POS not showing online orders for pickup | Ensure the customer selected Pick up in store at checkout and that the correct location is assigned to the POS device in Settings → Locations |
tools
Let shoppers save products to a wishlist, share it with friends, and get notified when saved items come back in stock or drop in price
development
Build a themeable storefront with design tokens and CSS custom properties that supports white-labeling, multi-brand variants, and dark mode
development
Speed up product discovery with instant search suggestions, fuzzy typo matching, and category-aware results powered by Algolia or Elasticsearch
development
Build a mobile-first storefront with thumb-friendly navigation, sticky add-to-cart buttons, and touch-optimized components for high mobile conversion