Hazır ERP bağlayıcıları kullanım durumlarının %70’ini kapsar. Geriye kalan %30 — özel alanlar, karmaşık fiyatlandırma kuralları, çoklu depo mantığı, onay iş akışları — özel bir bağlayıcı gerektirir. İşte güvenilir, sürdürülebilir ve üretime hazır bir bağlayıcı nasıl geliştirileceği. Açık konuşayım: bu %30, projelerin bütçesinin %70’ini yiyen kısımdır; hafife alma.
Mimari
Özel bir bağlayıcı, WooCommerce ve ERP’n arasında yer alan bir middleware servisidir:
┌─────────────┐ ┌──────────────────┐ ┌──────────┐
│ WooCommerce │────>│ Middleware │────>│ ERP │
│ REST API │<────│ (Node.js/Python)│<────│ API │
└─────────────┘ │ │ └──────────┘
│ ┌────────────┐ │
│ │ Queue │ │
│ │ (Redis) │ │
│ └────────────┘ │
│ ┌────────────┐ │
│ │ DB │ │
│ │ (mapping) │ │
│ └────────────┘ │
└──────────────────┘
Senkronizasyon Motoru
Bağlayıcının çekirdeği, veriyi her iki yönde işleyen bir senkronizasyon motorudur:
class SyncEngine {
constructor(wooClient, erpClient, db) {
this.woo = wooClient;
this.erp = erpClient;
this.db = db;
this.queue = new Queue('sync-jobs');
}
// Product sync: ERP → WooCommerce
async syncProducts() {
const lastSync = await this.db.getLastSync('products');
const erpProducts = await this.erp.getModifiedProducts(lastSync);
for (const product of erpProducts) {
await this.queue.add('sync-product', {
erpId: product.id,
data: product
});
}
}
// Order sync: WooCommerce → ERP
async syncOrders() {
const lastSync = await this.db.getLastSync('orders');
const wcOrders = await this.woo.getOrdersSince(lastSync);
for (const order of wcOrders) {
await this.queue.add('sync-order', {
wcOrderId: order.id,
data: order
});
}
}
}
Asıl zor kısım senkronizasyon değil, mutabakat
Bu kod güzel duruyor, ama izin ver de sektörün dinozoru bir uyarı bıraksın. Yıllar önce çok kullanıcılı oyunlar geliştiriyordum; sunucu ile istemcilerin durumu senkron tutması gereken türden işler. Orada öğrendiğim ders bugün burada birebir geçerli: özelliği yazmak hiçbir zaman zor kısım değildi. Zor kısım, iki taraf aynı anda farklı şey söylediğinde ne yaptığındı. Oyuncu ekranında düşman öldü, sunucuda hâlâ hayatta — şimdi hangisi doğru?
ERP ile WooCommerce arasında da aynı sahne yaşanıyor. Depoda stok 5’e düştü, aynı saniye online bir sipariş geldi; ERP “3 kaldı” diyor, WooCommerce “4 kaldı” diyor. Bu çatışmayı kim, hangi kurala göre çözüyor? Ben olsam bir bağlayıcının değerini feature listesine değil, tam olarak şu soruya nasıl cevap verdiğine bakarak ölçerim. İki ilke oyundan bugüne hiç değişmedi:
- Idempotency: Aynı mesajı iki kez işlemek, bir kez işlemekle aynı sonucu vermeli. Webhook iki kez tetiklenir, kuyruk bir işi yeniden dener — ve sen aynı siparişi ERP’ye iki kez düşürmüş olursun. Her yazma işlemini “bunu daha önce gördüm mü” kontrolüyle sar.
- Tek doğruluk kaynağı: Her alan için hangi sistemin patron olduğunu önceden karara bağla. Stok ERP’nin, sipariş WooCommerce’in; ortada kalan alan bırakma. Kararsız alan, gece 3’te seni uyandıran alandır.
Oyunda buna “reconciliation” derdik; kurumsal entegrasyonda “mutabakat”. İsim değişti, problem aynı kaldı. Bu bölümün geri kalanındaki kuyruk, hash ve eşleme tablosu numaralarının hepsi aslında bu tek derde verilmiş teknik cevaplar.
Veri Eşleme Katmanı
En kritik bileşen. Senkronizasyon mantığından ayrı bir eşleme konfigürasyonu oluştur:
// mapping.js
const PRODUCT_MAPPING = {
// ERP field → WooCommerce field
'item_code': 'sku',
'item_name': 'name',
'description': { field: 'description', transform: 'htmlEncode' },
'unit_price': { field: 'regular_price', transform: 'toString' },
'stock_qty': { field: 'stock_quantity', transform: 'parseInt' },
'is_active': { field: 'status', transform: (val) => val ? 'publish' : 'draft' },
'weight_kg': { field: 'weight', transform: 'toString' },
'category_code': { field: 'categories', transform: 'mapCategory' }
};
const ORDER_MAPPING = {
// WooCommerce field → ERP field
'id': 'external_ref',
'billing.email': 'customer_email',
'billing.first_name': { field: 'customer_name', transform: 'combineName' },
'line_items': { field: 'order_lines', transform: 'mapLineItems' },
'total': { field: 'order_total', transform: 'parseFloat' }
};
function applyMapping(source, mapping) {
const result = {};
for (const [sourceKey, target] of Object.entries(mapping)) {
const value = getNestedValue(source, sourceKey);
if (typeof target === 'string') {
result[target] = value;
} else {
const transformed = transforms[target.transform]
? transforms[target.transform](value, source)
: value;
result[target.field] = transformed;
}
}
return result;
}
Eşlemeyi koddan ayrı bir dosyada tutmanın sebebi şu: iş kuralları koddan çok daha hızlı değişir. Müşteri “artık ağırlığı gram olarak istiyoruz” dediğinde senkronizasyon motoruna dokunmadan tek satır düzeltmek istersin. Ben mantığı ve konfigürasyonu birbirine karıştırılmış nice bağlayıcının bakım masasında öldüğünü gördüm.
Kuyruk Tabanlı İşleme
Senkronizasyonları asla satır içinde işleme. Güvenilirlik için iş kuyruğu kullan:
const Queue = require('bull');
const syncQueue = new Queue('erp-sync', {
redis: { host: '127.0.0.1', port: 6379 },
defaultJobOptions: {
attempts: 3,
backoff: { type: 'exponential', delay: 2000 },
removeOnComplete: 100,
removeOnFail: 500
}
});
// Process product sync jobs
syncQueue.process('sync-product', async (job) => {
const { erpId, data } = job.data;
// Check if product exists in WooCommerce
const mapping = await db.getMapping('product', erpId);
if (mapping) {
// Update existing product
await wooClient.updateProduct(mapping.wc_id, applyMapping(data, PRODUCT_MAPPING));
} else {
// Create new product
const wcProduct = await wooClient.createProduct(applyMapping(data, PRODUCT_MAPPING));
await db.createMapping('product', erpId, wcProduct.id);
}
return { success: true, erpId };
});
// Handle failures
syncQueue.on('failed', (job, err) => {
console.error(`Job ${job.id} failed: ${err.message}`);
if (job.attemptsMade >= job.opts.attempts) {
alertTeam(`Sync failed permanently: ${job.data.erpId} - ${err.message}`);
}
});
Kuyruğun sana bedavaya verdiği şey, biraz önce konuştuğumuz idempotency’nin altyapısı: yeniden deneme, exponential backoff ve kalıcı başarısızlıkta ekibi uyaran bir mekanizma. ERP bir dakikalığına düşse bile işler kuyrukta bekler, geri geldiğinde kaldığı yerden devam eder. Satır içi işlemede bu lüksün yok — ilk zaman aşımı, kaybolmuş bir sipariş demek.
ID Eşleme Tablosu
WooCommerce ID’leri ve ERP ID’leri farklıdır. Bir eşleme tablosu tut:
CREATE TABLE entity_mapping (
id INT AUTO_INCREMENT PRIMARY KEY,
entity_type ENUM('product', 'customer', 'order', 'category') NOT NULL,
erp_id VARCHAR(100) NOT NULL,
wc_id INT NOT NULL,
last_synced_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
sync_hash VARCHAR(64), -- Hash of data to detect changes
UNIQUE KEY (entity_type, erp_id),
INDEX (entity_type, wc_id)
);
sync_hash alanı gereksiz güncellemeleri önler. ERP verisi son senkronizasyondan bu yana değişmemişse, WooCommerce API çağrısını atla. Bu küçük numara, on binlerce ürünlü kataloglarda API oran limitine takılmakla rahat çalışmak arasındaki farktır.
function hasChanged(currentData, storedHash) {
const currentHash = crypto.createHash('md5')
.update(JSON.stringify(currentData))
.digest('hex');
return currentHash !== storedHash;
}
Webhook Alıcısı
Gerçek zamanlı sipariş senkronizasyonu için WooCommerce webhook’larını al:
app.post('/webhooks/woocommerce/order-created', (req, res) => {
// Verify webhook signature
const signature = req.headers['x-wc-webhook-signature'];
const expected = crypto.createHmac('sha256', WEBHOOK_SECRET)
.update(JSON.stringify(req.body))
.digest('base64');
if (signature !== expected) {
return res.status(401).send('Invalid signature');
}
// Queue the order for processing
syncQueue.add('sync-order', {
wcOrderId: req.body.id,
data: req.body
});
res.status(200).send('OK');
});
Dikkat: webhook imza doğrulaması pazarlık konusu değil. İmzayı doğrulamayan bir uç nokta, herkesin ERP’ne sahte sipariş sokabileceği açık bir kapıdır. Ayrıca WooCommerce’in webhook’u iki kez göndermesi gayet normaldir — işte tam da bu yüzden yukarıda idempotency’nin altını çizdim.
İzleme Panosu
Senkronizasyon sağlığını takip etmek için basit bir pano oluştur:
app.get('/api/sync/status', async (req, res) => {
const [productCount] = await db.query('SELECT COUNT(*) as count FROM entity_mapping WHERE entity_type = "product"');
const [orderCount] = await db.query('SELECT COUNT(*) as count FROM entity_mapping WHERE entity_type = "order"');
const [recentFails] = await db.query('SELECT * FROM sync_log WHERE status = "failed" AND created_at > DATE_SUB(NOW(), INTERVAL 1 HOUR)');
const queueStats = await syncQueue.getJobCounts();
res.json({
mappedProducts: productCount[0].count,
mappedOrders: orderCount[0].count,
queue: queueStats,
recentFailures: recentFails.length,
lastProductSync: await db.getLastSync('products'),
lastOrderSync: await db.getLastSync('orders')
});
});
Bu panoyu “olsa iyi olur” listesine atma; ben olsam ilk günden kurardım. Bir entegrasyonun sessizce bozulması, gürültülü bozulmasından çok daha pahalıdır — çünkü hatayı müşteri, senden önce, eksik siparişle fark eder.
Deployment
Bağlayıcını bağımsız bir servis olarak deploy et:
- Tutarlı ortamlar için Docker
- Süreç yönetimi için PM2 veya systemd
- Uptime izleme için sağlık kontrolü endpoint’i
- Winston + CloudWatch veya ELK ile log toplama
Bağlayıcıyı WordPress’in içine bir eklenti olarak gömme isteğine direnmeni öneririm. Ayrı bir servis olarak yaşadığında bağımsız ölçeklenir, WordPress bir güncellemede tökezlese bile senkronizasyon ayakta kalır, ve loglarını karıştırmadan izleyebilirsin. Bu, uzun vadede kendini ödeyen sıkıcı bir karardır.
Sonuç
Özel bir WooCommerce-ERP bağlayıcısı önemli bir mühendislik yatırımıdır, ancak entegrasyon gereksinimlerin hazır çözümlerin sunduklarını aştığında kendini amorti eder. Temel bileşenler — veri eşleme, kuyruk işleme, ID eşleme, hata işleme ve izleme — hangi ERP’ye bağlanıyor olursan ol evrenseldir. Bunları bir kez iyi oluştur ve değişen iş gereksinimlerine uyum sağlayabilecek bir temele sahip olursun.
Son bir söz: bu işte kahramanlık, en gösterişli özellikte değil, iki sistem birbirine ters düştüğünde sistemin sakin kalmasında saklıdır. Çok kullanıcılı oyunlarda da öyleydi, kurumsal entegrasyonda da öyle. Mutabakatı çözersen, gerisi zaten detaydır.
Last modified: Ağustos 2, 2026
United States / English
Slovensko / Slovenčina
Canada / Français
Türkiye / Türkçe