Automatizarea e-commerce transforma procesarea manuala a comenzilor in workflow-uri scalabile si fara erori. Acest ghid acopera construirea de automatizari Shopify pregatite pentru productie cu n8n pentru gestionarea comenzilor, controlul stocurilor si comunicarea cu clientii.
Configurarea integrarii Shopify
Configurare API
// Shopify API Configuration
// Store in n8n credentials
{
"shopifyApi": {
"apiKey": "{{ $env.SHOPIFY_API_KEY }}",
"password": "{{ $env.SHOPIFY_API_PASSWORD }}",
"shopSubdomain": "{{ $env.SHOPIFY_STORE_NAME }}",
"apiVersion": "2024-01"
}
}
// Required API Scopes:
// - read_orders, write_orders
// - read_products, write_products
// - read_inventory, write_inventory
// - read_customers, write_customers
// - read_fulfillments, write_fulfillmentsConfigurare Webhook
// Function Node: Shopify Webhook Handler
const webhookData = $input.first().json;
const topic = $input.first().headers['x-shopify-topic'];
const hmac = $input.first().headers['x-shopify-hmac-sha256'];
// Verify webhook signature
const crypto = require('crypto');
const secret = $env.SHOPIFY_WEBHOOK_SECRET;
const body = JSON.stringify(webhookData);
const calculatedHmac = crypto
.createHmac('sha256', secret)
.update(body, 'utf8')
.digest('base64');
if (hmac !== calculatedHmac) {
throw new Error('Invalid webhook signature');
}
// Parse webhook topic
const [resource, action] = topic.split('/');
return [{
json: {
resource,
action,
data: webhookData,
receivedAt: new Date().toISOString(),
verified: true
}
}];Workflow-uri de procesare a comenzilor
Handler pentru comenzi noi
// Function Node: Process New Order
const order = $input.first().json.data;
// Extract order details
const orderData = {
orderId: order.id,
orderNumber: order.order_number,
orderName: order.name,
createdAt: order.created_at,
// Customer info
customer: {
id: order.customer?.id,
email: order.customer?.email || order.email,
firstName: order.customer?.first_name,
lastName: order.customer?.last_name,
phone: order.customer?.phone || order.phone,
totalOrders: order.customer?.orders_count || 1,
totalSpent: order.customer?.total_spent || order.total_price
},
// Order totals
financials: {
subtotal: parseFloat(order.subtotal_price),
totalTax: parseFloat(order.total_tax),
totalShipping: parseFloat(order.total_shipping_price_set?.shop_money?.amount || 0),
totalDiscount: parseFloat(order.total_discounts),
totalPrice: parseFloat(order.total_price),
currency: order.currency
},
// Line items
items: order.line_items.map(item => ({
id: item.id,
productId: item.product_id,
variantId: item.variant_id,
title: item.title,
variantTitle: item.variant_title,
sku: item.sku,
quantity: item.quantity,
price: parseFloat(item.price),
totalDiscount: parseFloat(item.total_discount),
requiresShipping: item.requires_shipping,
fulfillmentStatus: item.fulfillment_status
})),
// Shipping info
shipping: {
method: order.shipping_lines[0]?.title,
address: order.shipping_address ? {
name: `${order.shipping_address.first_name} ${order.shipping_address.last_name}`,
company: order.shipping_address.company,
address1: order.shipping_address.address1,
address2: order.shipping_address.address2,
city: order.shipping_address.city,
province: order.shipping_address.province,
country: order.shipping_address.country,
zip: order.shipping_address.zip,
phone: order.shipping_address.phone
} : null
},
// Flags
flags: {
isFirstOrder: (order.customer?.orders_count || 1) === 1,
hasDiscount: parseFloat(order.total_discounts) > 0,
isPaid: order.financial_status === 'paid',
requiresShipping: order.line_items.some(item => item.requires_shipping),
hasFraudRisk: order.fraud_analysis?.risk_level === 'high'
},
// Discount codes
discountCodes: order.discount_codes || [],
// Tags and notes
tags: order.tags ? order.tags.split(',').map(t => t.trim()) : [],
note: order.note,
noteAttributes: order.note_attributes
};
return [{ json: orderData }];Logica de rutare a comenzilor
// Function Node: Route Order by Criteria
const order = $input.first().json;
// Determine fulfillment route
let route = 'standard';
let priority = 'normal';
let warehouse = 'main';
// High value orders
if (order.financials.totalPrice > 500) {
priority = 'high';
}
// First-time customer
if (order.flags.isFirstOrder) {
order.flags.requiresWelcomeEmail = true;
}
// Fraud risk
if (order.flags.hasFraudRisk) {
route = 'manual_review';
priority = 'hold';
}
// Express shipping
const shippingMethod = order.shipping.method?.toLowerCase() || '';
if (shippingMethod.includes('express') || shippingMethod.includes('overnight')) {
priority = 'urgent';
}
// International orders
if (order.shipping.address?.country !== 'United States') {
route = 'international';
warehouse = 'international';
}
// Specific product routing
const requiresSpecialHandling = order.items.some(item =>
item.tags?.includes('fragile') ||
item.tags?.includes('hazmat') ||
item.sku?.startsWith('SPECIAL-')
);
if (requiresSpecialHandling) {
route = 'special_handling';
}
return [{
json: {
...order,
routing: {
route,
priority,
warehouse,
assignedAt: new Date().toISOString()
}
}
}];Gestionarea stocurilor
Sincronizare stocuri in timp real
// Function Node: Inventory Level Handler
const inventoryUpdate = $input.first().json;
// Process inventory level change
const inventoryData = {
inventoryItemId: inventoryUpdate.inventory_item_id,
locationId: inventoryUpdate.location_id,
available: inventoryUpdate.available,
updatedAt: inventoryUpdate.updated_at
};
// Check for low stock threshold
const lowStockThreshold = 10;
const criticalStockThreshold = 3;
let stockStatus = 'normal';
let alertRequired = false;
if (inventoryData.available <= 0) {
stockStatus = 'out_of_stock';
alertRequired = true;
} else if (inventoryData.available <= criticalStockThreshold) {
stockStatus = 'critical';
alertRequired = true;
} else if (inventoryData.available <= lowStockThreshold) {
stockStatus = 'low';
alertRequired = true;
}
return [{
json: {
...inventoryData,
stockStatus,
alertRequired,
thresholds: {
low: lowStockThreshold,
critical: criticalStockThreshold
}
}
}];Agregarea stocurilor din mai multe locatii
// Function Node: Aggregate Inventory Across Locations
const inventoryLevels = $input.all().map(item => item.json);
// Group by inventory item
const aggregated = {};
for (const level of inventoryLevels) {
const itemId = level.inventory_item_id;
if (!aggregated[itemId]) {
aggregated[itemId] = {
inventoryItemId: itemId,
totalAvailable: 0,
totalOnHand: 0,
locations: [],
lastUpdated: level.updated_at
};
}
aggregated[itemId].totalAvailable += level.available || 0;
aggregated[itemId].totalOnHand += level.on_hand || 0;
aggregated[itemId].locations.push({
locationId: level.location_id,
locationName: level.location_name,
available: level.available,
onHand: level.on_hand,
incoming: level.incoming || 0
});
// Track most recent update
if (level.updated_at > aggregated[itemId].lastUpdated) {
aggregated[itemId].lastUpdated = level.updated_at;
}
}
// Convert to array and calculate status
const results = Object.values(aggregated).map(item => ({
...item,
status: item.totalAvailable <= 0 ? 'out_of_stock' :
item.totalAvailable <= 10 ? 'low_stock' : 'in_stock',
needsReorder: item.totalAvailable <= 20
}));
return results.map(item => ({ json: item }));Workflow automat de reaprovizionare
// Function Node: Generate Purchase Order
const lowStockItems = $input.all().map(item => item.json);
// Group items by supplier
const bySupplier = {};
for (const item of lowStockItems) {
const supplier = item.supplier || 'default';
if (!bySupplier[supplier]) {
bySupplier[supplier] = {
supplier,
items: [],
totalItems: 0,
estimatedCost: 0
};
}
// Calculate reorder quantity
const reorderPoint = item.reorderPoint || 20;
const reorderQuantity = item.reorderQuantity || 50;
const currentStock = item.totalAvailable;
const quantityToOrder = Math.max(0, reorderQuantity - currentStock + reorderPoint);
if (quantityToOrder > 0) {
bySupplier[supplier].items.push({
sku: item.sku,
productTitle: item.productTitle,
currentStock,
reorderQuantity: quantityToOrder,
unitCost: item.unitCost || 0,
totalCost: quantityToOrder * (item.unitCost || 0)
});
bySupplier[supplier].totalItems += quantityToOrder;
bySupplier[supplier].estimatedCost += quantityToOrder * (item.unitCost || 0);
}
}
// Generate purchase orders
const purchaseOrders = Object.values(bySupplier)
.filter(po => po.items.length > 0)
.map((po, index) => ({
...po,
poNumber: `PO-${Date.now()}-${index + 1}`,
createdAt: new Date().toISOString(),
status: 'draft',
expectedDelivery: new Date(Date.now() + 7 * 24 * 60 * 60 * 1000).toISOString()
}));
return purchaseOrders.map(po => ({ json: po }));Automatizarea comunicarii cu clientii
Email de confirmare comanda
// Function Node: Generate Order Confirmation Email
const order = $input.first().json;
// Format line items for email
const itemsHtml = order.items.map(item => `
<tr>
<td style="padding: 10px; border-bottom: 1px solid #eee;">
${item.title}${item.variantTitle ? ` - ${item.variantTitle}` : ''}
</td>
<td style="padding: 10px; border-bottom: 1px solid #eee; text-align: center;">
${item.quantity}
</td>
<td style="padding: 10px; border-bottom: 1px solid #eee; text-align: right;">
$${(item.price * item.quantity).toFixed(2)}
</td>
</tr>
`).join('');
// Format shipping address
const address = order.shipping.address;
const addressHtml = address ? `
<p>
${address.name}<br>
${address.company ? address.company + '<br>' : ''}
${address.address1}<br>
${address.address2 ? address.address2 + '<br>' : ''}
${address.city}, ${address.province} ${address.zip}<br>
${address.country}
</p>
` : '<p>Digital delivery - no shipping required</p>';
const emailHtml = `
<!DOCTYPE html>
<html>
<head>
<style>
body { font-family: Arial, sans-serif; max-width: 600px; margin: 0 auto; }
.header { background: #2563eb; color: white; padding: 20px; text-align: center; }
.content { padding: 20px; }
table { width: 100%; border-collapse: collapse; }
th { background: #f3f4f6; padding: 10px; text-align: left; }
.totals { margin-top: 20px; text-align: right; }
.footer { background: #f9fafb; padding: 20px; text-align: center; font-size: 12px; }
</style>
</head>
<body>
<div class="header">
<h1>Order Confirmed!</h1>
<p>Order ${order.orderName}</p>
</div>
<div class="content">
<p>Hi ${order.customer.firstName || 'there'},</p>
<p>Thank you for your order! We've received it and are getting it ready.</p>
<h3>Order Details</h3>
<table>
<thead>
<tr>
<th>Item</th>
<th style="text-align: center;">Qty</th>
<th style="text-align: right;">Price</th>
</tr>
</thead>
<tbody>
${itemsHtml}
</tbody>
</table>
<div class="totals">
<p>Subtotal: $${order.financials.subtotal.toFixed(2)}</p>
${order.financials.totalDiscount > 0 ? `<p>Discount: -$${order.financials.totalDiscount.toFixed(2)}</p>` : ''}
<p>Shipping: $${order.financials.totalShipping.toFixed(2)}</p>
<p>Tax: $${order.financials.totalTax.toFixed(2)}</p>
<p><strong>Total: $${order.financials.totalPrice.toFixed(2)} ${order.financials.currency}</strong></p>
</div>
<h3>Shipping Address</h3>
${addressHtml}
<p>You'll receive another email when your order ships with tracking information.</p>
</div>
<div class="footer">
<p>Questions? Reply to this email or contact us at support@example.com</p>
</div>
</body>
</html>
`;
return [{
json: {
to: order.customer.email,
subject: `Order Confirmed - ${order.orderName}`,
html: emailHtml,
tags: ['order-confirmation', `order-${order.orderId}`]
}
}];Notificare de livrare
// Function Node: Generate Shipping Notification
const fulfillment = $input.first().json;
const trackingUrl = fulfillment.tracking_url ||
(fulfillment.tracking_company === 'USPS' ?
`https://tools.usps.com/go/TrackConfirmAction?tLabels=${fulfillment.tracking_number}` :
fulfillment.tracking_company === 'UPS' ?
`https://www.ups.com/track?tracknum=${fulfillment.tracking_number}` :
fulfillment.tracking_company === 'FedEx' ?
`https://www.fedex.com/fedextrack/?trknbr=${fulfillment.tracking_number}` :
null);
const emailHtml = `
<!DOCTYPE html>
<html>
<head>
<style>
body { font-family: Arial, sans-serif; max-width: 600px; margin: 0 auto; }
.header { background: #059669; color: white; padding: 20px; text-align: center; }
.content { padding: 20px; }
.tracking-box { background: #f3f4f6; padding: 20px; border-radius: 8px; margin: 20px 0; text-align: center; }
.track-button { display: inline-block; background: #2563eb; color: white; padding: 12px 24px; text-decoration: none; border-radius: 6px; margin-top: 10px; }
.footer { background: #f9fafb; padding: 20px; text-align: center; font-size: 12px; }
</style>
</head>
<body>
<div class="header">
<h1>Your Order Has Shipped!</h1>
</div>
<div class="content">
<p>Great news! Your order is on its way.</p>
<div class="tracking-box">
<p><strong>Carrier:</strong> ${fulfillment.tracking_company || 'Standard Shipping'}</p>
${fulfillment.tracking_number ? `<p><strong>Tracking Number:</strong> ${fulfillment.tracking_number}</p>` : ''}
${trackingUrl ? `<a href="${trackingUrl}" class="track-button">Track Your Package</a>` : ''}
</div>
<h3>Items Shipped</h3>
<ul>
${fulfillment.line_items.map(item =>
`<li>${item.title}${item.variant_title ? ` - ${item.variant_title}` : ''} (x${item.quantity})</li>`
).join('')}
</ul>
<p>Estimated delivery: ${fulfillment.estimated_delivery_at ?
new Date(fulfillment.estimated_delivery_at).toLocaleDateString() :
'3-5 business days'}</p>
</div>
<div class="footer">
<p>Questions about your shipment? Contact us at support@example.com</p>
</div>
</body>
</html>
`;
return [{
json: {
to: fulfillment.customer_email,
subject: 'Your Order Has Shipped!',
html: emailHtml,
tags: ['shipping-notification', `order-${fulfillment.order_id}`]
}
}];Automatizarea livrarilor
Integrarea serviciului de livrare
// Function Node: Create Fulfillment Request
const order = $input.first().json;
// Prepare fulfillment request for shipping carrier
const fulfillmentRequest = {
orderId: order.orderId,
orderNumber: order.orderNumber,
shipTo: {
name: order.shipping.address.name,
company: order.shipping.address.company,
street1: order.shipping.address.address1,
street2: order.shipping.address.address2,
city: order.shipping.address.city,
state: order.shipping.address.province,
postalCode: order.shipping.address.zip,
country: order.shipping.address.country,
phone: order.shipping.address.phone
},
shipFrom: {
name: 'Your Store',
street1: '123 Warehouse St',
city: 'Commerce City',
state: 'CA',
postalCode: '90210',
country: 'United States',
phone: '555-123-4567'
},
packages: [{
weight: order.items.reduce((sum, item) =>
sum + (item.weight || 0.5) * item.quantity, 0
),
weightUnit: 'lb',
dimensions: {
length: 10,
width: 8,
height: 4,
unit: 'in'
}
}],
items: order.items.map(item => ({
sku: item.sku,
description: item.title,
quantity: item.quantity,
value: item.price,
weight: item.weight || 0.5
})),
serviceType: mapShippingService(order.shipping.method),
shipDate: new Date().toISOString().split('T')[0],
reference: order.orderNumber,
options: {
signatureRequired: order.financials.totalPrice > 100,
insurance: order.financials.totalPrice > 200 ? {
amount: order.financials.totalPrice,
currency: order.financials.currency
} : null
}
};
function mapShippingService(shopifyMethod) {
const mappings = {
'Standard Shipping': 'ground',
'Express Shipping': 'express',
'Overnight': 'overnight',
'Free Shipping': 'ground'
};
return mappings[shopifyMethod] || 'ground';
}
return [{ json: fulfillmentRequest }];Actualizarea livrarii in Shopify
// Function Node: Update Shopify with Fulfillment
const carrierResponse = $input.first().json;
const originalOrder = $input.first().json.orderData;
// Build Shopify fulfillment payload
const fulfillmentPayload = {
fulfillment: {
location_id: $env.SHOPIFY_LOCATION_ID,
tracking_number: carrierResponse.trackingNumber,
tracking_company: carrierResponse.carrier,
tracking_url: carrierResponse.trackingUrl,
notify_customer: true,
line_items: originalOrder.items.map(item => ({
id: item.id,
quantity: item.quantity
}))
}
};
return [{
json: {
endpoint: `/admin/api/2024-01/orders/${originalOrder.orderId}/fulfillments.json`,
method: 'POST',
payload: fulfillmentPayload
}
}];Analiza si raportare
Sumar zilnic al vanzarilor
// Function Node: Generate Daily Sales Report
const orders = $input.all().map(item => item.json);
// Calculate metrics
const metrics = {
date: new Date().toISOString().split('T')[0],
totalOrders: orders.length,
totalRevenue: 0,
averageOrderValue: 0,
totalItems: 0,
newCustomers: 0,
returningCustomers: 0,
topProducts: {},
revenueByHour: new Array(24).fill(0),
ordersByCountry: {}
};
for (const order of orders) {
// Revenue
metrics.totalRevenue += order.financials.totalPrice;
// Items
metrics.totalItems += order.items.reduce((sum, item) => sum + item.quantity, 0);
// Customer type
if (order.flags.isFirstOrder) {
metrics.newCustomers++;
} else {
metrics.returningCustomers++;
}
// Product popularity
for (const item of order.items) {
const key = item.sku || item.title;
if (!metrics.topProducts[key]) {
metrics.topProducts[key] = { title: item.title, quantity: 0, revenue: 0 };
}
metrics.topProducts[key].quantity += item.quantity;
metrics.topProducts[key].revenue += item.price * item.quantity;
}
// Revenue by hour
const hour = new Date(order.createdAt).getHours();
metrics.revenueByHour[hour] += order.financials.totalPrice;
// Geography
const country = order.shipping.address?.country || 'Unknown';
metrics.ordersByCountry[country] = (metrics.ordersByCountry[country] || 0) + 1;
}
// Calculate averages
metrics.averageOrderValue = metrics.totalOrders > 0 ?
metrics.totalRevenue / metrics.totalOrders : 0;
// Sort top products
metrics.topProducts = Object.entries(metrics.topProducts)
.sort((a, b) => b[1].revenue - a[1].revenue)
.slice(0, 10)
.map(([sku, data]) => ({ sku, ...data }));
return [{ json: metrics }];Gestionarea erorilor si monitorizare
Handler de erori pentru workflow
// Function Node: E-commerce Error Handler
const error = $input.first().json.error;
const context = $input.first().json.context;
// Categorize error
let category = 'unknown';
let severity = 'medium';
let action = 'log';
const errorMessage = error.message?.toLowerCase() || '';
if (errorMessage.includes('rate limit') || errorMessage.includes('429')) {
category = 'rate_limit';
severity = 'low';
action = 'retry_with_backoff';
} else if (errorMessage.includes('not found') || errorMessage.includes('404')) {
category = 'not_found';
severity = 'medium';
action = 'skip';
} else if (errorMessage.includes('unauthorized') || errorMessage.includes('401')) {
category = 'auth';
severity = 'high';
action = 'alert';
} else if (errorMessage.includes('inventory') || errorMessage.includes('stock')) {
category = 'inventory';
severity = 'high';
action = 'manual_review';
} else if (errorMessage.includes('payment')) {
category = 'payment';
severity = 'critical';
action = 'alert_urgent';
}
const errorReport = {
timestamp: new Date().toISOString(),
category,
severity,
action,
error: {
message: error.message,
code: error.code,
stack: error.stack?.slice(0, 500)
},
context: {
orderId: context?.orderId,
operation: context?.operation,
workflowId: $workflow.id,
executionId: $execution.id
}
};
return [{
json: {
...errorReport,
shouldRetry: action.includes('retry'),
shouldAlert: action.includes('alert'),
alertChannel: severity === 'critical' ? 'pagerduty' : 'slack'
}
}];Sumar de bune practici
- Valideaza webhook-urile folosind semnaturi HMAC
- Gestioneaza rate limits cu exponential backoff
- Foloseste chei de idempotenta pentru a preveni procesarea duplicata
- Monitorizeaza stocurile in timp real pe toate locatiile
- Personalizeaza comunicarile in functie de istoricul clientului
- Implementeaza gestionarea erorilor cu escaladare corespunzatoare
- Urmareste analiza pentru informatii de business
- Testeaza in detaliu inainte de lansarea in productie
Automatizarea e-commerce cu n8n permite scalarea operatiunilor mentinand calitatea experientei clientilor. Incepe cu procesarea de baza a comenzilor si extinde catre automatizarea stocurilor si marketingului pe masura ce afacerea creste.
Sistemul tau AI e conform cu EU AI Act? Evaluare gratuita de risc - afla in 2 minute →