Skip to content

Inventory Sync

Keep product data synchronized between Pixlpay and external systems.

Overview

This pattern shows how to:

  1. Sync products from Pixlpay to external systems
  2. Keep data in sync using webhooks
  3. Handle conflicts and updates

Full Sync

Periodically sync all products:

javascript
const { PixlpayClient } = require('./pixlpay');

const pixlpay = new PixlpayClient(
  process.env.PIXLPAY_STORE_URL,
  process.env.PIXLPAY_API_TOKEN
);

async function syncAllProducts() {
  console.log('Starting full product sync...');

  let page = 1;
  let totalSynced = 0;

  while (true) {
    const response = await pixlpay.getProducts({ page, per_page: 100 });

    for (const product of response.data) {
      await syncProduct(product);
      totalSynced++;
    }

    if (page >= response.meta.last_page) break;
    page++;
  }

  console.log(`Synced ${totalSynced} products`);
}

async function syncProduct(product) {
  await externalSystem.upsert('products', {
    external_id: `pixlpay_${product.id}`,
    name: product.name,
    description: product.description,
    price: parseFloat(product.price),
    active: product.is_active,
    type: product.type,
    image_url: product.images?.[0]?.url,
    updated_at: product.updated_at,
  });
}

// Run daily
setInterval(syncAllProducts, 24 * 60 * 60 * 1000);

Real-Time Sync with Webhooks

Keep data in sync as changes happen:

javascript
app.post('/webhooks/pixlpay', express.raw({ type: 'application/json' }), async (req, res) => {
  // Verify signature...

  const event = JSON.parse(req.body);

  switch (event.event_type) {
    case 'product.created':
      await handleProductCreated(event.data);
      break;
    case 'product.updated':
      await handleProductUpdated(event.data);
      break;
    case 'product.deleted':
      await handleProductDeleted(event.data);
      break;
  }

  res.status(200).json({ status: 'received' });
});

async function handleProductCreated(data) {
  const product = await pixlpay.getProduct(data.product_id);
  await syncProduct(product.data);
  console.log(`Created: ${product.data.name}`);
}

async function handleProductUpdated(data) {
  const product = await pixlpay.getProduct(data.product_id);
  await syncProduct(product.data);
  console.log(`Updated: ${product.data.name}`);
}

async function handleProductDeleted(data) {
  await externalSystem.delete('products', {
    external_id: `pixlpay_${data.product_id}`
  });
  console.log(`Deleted: product ${data.product_id}`);
}

Bidirectional Sync

Sync changes from external system back to Pixlpay:

javascript
// When external system updates a product
async function onExternalProductUpdate(externalProduct) {
  // Check if it's a Pixlpay product
  if (!externalProduct.external_id?.startsWith('pixlpay_')) {
    return;
  }

  const pixlpayId = externalProduct.external_id.replace('pixlpay_', '');

  // Update in Pixlpay (when write endpoints available)
  await pixlpay.updateProduct(pixlpayId, {
    name: externalProduct.name,
    price: externalProduct.price,
    is_active: externalProduct.active,
  });
}

Conflict Resolution

Handle conflicts when both systems update:

javascript
async function syncWithConflictResolution(pixlpayProduct, externalProduct) {
  const pixlpayUpdated = new Date(pixlpayProduct.updated_at);
  const externalUpdated = new Date(externalProduct.updated_at);

  if (pixlpayUpdated > externalUpdated) {
    // Pixlpay is newer, update external
    await externalSystem.update(externalProduct.id, {
      name: pixlpayProduct.name,
      price: pixlpayProduct.price,
    });
  } else if (externalUpdated > pixlpayUpdated) {
    // External is newer, update Pixlpay
    await pixlpay.updateProduct(pixlpayProduct.id, {
      name: externalProduct.name,
      price: externalProduct.price,
    });
  }
  // If equal, no action needed
}

Sync Status Tracking

Track sync status for debugging:

javascript
const syncStatus = {
  lastFullSync: null,
  lastWebhookReceived: null,
  productsInSync: 0,
  productsOutOfSync: 0,
  errors: [],
};

async function checkSyncStatus() {
  const pixlpayProducts = await getAllProducts();
  const externalProducts = await externalSystem.getAll('products');

  syncStatus.productsInSync = 0;
  syncStatus.productsOutOfSync = 0;

  for (const pp of pixlpayProducts) {
    const ep = externalProducts.find(e => e.external_id === `pixlpay_${pp.id}`);

    if (!ep) {
      syncStatus.productsOutOfSync++;
    } else if (pp.updated_at !== ep.pixlpay_updated_at) {
      syncStatus.productsOutOfSync++;
    } else {
      syncStatus.productsInSync++;
    }
  }

  return syncStatus;
}

Best Practices

  1. Use webhooks for real-time - Don't poll frequently
  2. Run full sync periodically - Catch any missed webhooks
  3. Track sync metadata - Store updated_at for conflict resolution
  4. Handle deletions carefully - Soft delete to allow recovery
  5. Log all sync operations - Audit trail for debugging

Built for game developers, by game developers.