Skip to content

Node.js Examples

Complete Node.js examples for integrating with the Pixlpay API.

API Client Class

javascript
const axios = require('axios');
const crypto = require('crypto');

class PixlpayClient {
  constructor(storeUrl, token) {
    this.client = axios.create({
      baseURL: `${storeUrl.replace(/\/$/, '')}/api/external/v1`,
      headers: {
        'Authorization': `Bearer ${token}`,
        'Accept': 'application/json',
        'Content-Type': 'application/json',
      },
      timeout: 30000,
    });

    this.client.interceptors.response.use(
      response => response.data,
      error => {
        if (error.response) {
          const { data, status } = error.response;
          throw new PixlpayError(
            data.message || 'Unknown error',
            data.error || 'UNKNOWN',
            status,
            data.errors
          );
        }
        throw error;
      }
    );
  }

  // Products
  async getProducts(params = {}) {
    return this.client.get('/products', { params });
  }

  async getProduct(id) {
    return this.client.get(`/products/${id}`);
  }

  // Orders
  async getOrders(params = {}) {
    return this.client.get('/orders', { params });
  }

  async getOrder(id) {
    return this.client.get(`/orders/${id}`);
  }

  async fulfillOrder(id) {
    return this.client.post(`/orders/${id}/fulfill`);
  }

  // Customers
  async getCustomers(params = {}) {
    return this.client.get('/customers', { params });
  }

  async getCustomer(id) {
    return this.client.get(`/customers/${id}`);
  }

  // Analytics
  async getRevenueAnalytics(params = {}) {
    return this.client.get('/analytics/revenue', { params });
  }

  async getSalesAnalytics(params = {}) {
    return this.client.get('/analytics/sales', { params });
  }

  // Webhooks
  async getWebhooks() {
    return this.client.get('/webhooks');
  }

  async createWebhook(url, events) {
    return this.client.post('/webhooks', { url, events });
  }

  async deleteWebhook(id) {
    return this.client.delete(`/webhooks/${id}`);
  }
}

class PixlpayError extends Error {
  constructor(message, code, status, errors = null) {
    super(message);
    this.name = 'PixlpayError';
    this.code = code;
    this.status = status;
    this.errors = errors;
  }

  isValidationError() {
    return this.code === 'VALIDATION_ERROR';
  }

  isRateLimited() {
    return this.status === 429;
  }
}

module.exports = { PixlpayClient, PixlpayError };

Usage Examples

List Products

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

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

async function listProducts() {
  try {
    const response = await client.getProducts({
      is_active: true,
      per_page: 50,
    });

    for (const product of response.data) {
      console.log(`${product.name} - $${product.price}`);
    }
  } catch (error) {
    console.error('Error:', error.message);
  }
}

listProducts();

Fulfill an Order

javascript
async function fulfillOrder(orderId) {
  try {
    const result = await client.fulfillOrder(orderId);
    console.log(`Order ${result.data.order_number} fulfilled!`);
  } catch (error) {
    if (error.code === 'ORDER_ALREADY_COMPLETED') {
      console.log('Order was already completed');
    } else {
      console.error('Error:', error.message);
    }
  }
}

Get All Orders with Pagination

javascript
async function getAllOrders() {
  const allOrders = [];
  let page = 1;
  let hasMore = true;

  while (hasMore) {
    const response = await client.getOrders({
      page,
      per_page: 100,
      payment_status: 'paid',
    });

    allOrders.push(...response.data);
    hasMore = page < response.meta.last_page;
    page++;
  }

  return allOrders;
}

Express Webhook Handler

javascript
const express = require('express');
const crypto = require('crypto');

const app = express();

// Use raw body for signature verification
app.use('/webhooks/pixlpay', express.raw({ type: 'application/json' }));

function verifySignature(payload, signature, secret) {
  const expected = crypto
    .createHmac('sha256', secret)
    .update(payload)
    .digest('hex');

  return crypto.timingSafeEqual(
    Buffer.from(expected),
    Buffer.from(signature)
  );
}

app.post('/webhooks/pixlpay', async (req, res) => {
  const signature = req.headers['x-webhook-signature'];
  const secret = process.env.PIXLPAY_WEBHOOK_SECRET;

  // Verify signature
  if (!verifySignature(req.body, signature, secret)) {
    console.error('Invalid webhook signature');
    return res.status(401).json({ error: 'Invalid signature' });
  }

  const event = JSON.parse(req.body);
  console.log(`Received: ${event.event_type} (${event.id})`);

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

  // Process asynchronously
  try {
    await processEvent(event);
  } catch (error) {
    console.error('Error processing webhook:', error);
  }
});

async function processEvent(event) {
  switch (event.event_type) {
    case 'order.paid':
      await handleOrderPaid(event.data);
      break;
    case 'order.refunded':
      await handleOrderRefunded(event.data);
      break;
    case 'subscription.created':
      await handleSubscriptionCreated(event.data);
      break;
    case 'subscription.cancelled':
      await handleSubscriptionCancelled(event.data);
      break;
    default:
      console.log(`Unhandled event: ${event.event_type}`);
  }
}

async function handleOrderPaid(data) {
  const { order_id, customer_email, items } = data;

  for (const item of items) {
    await deliverItem(customer_email, item);
  }

  console.log(`Delivered order ${order_id} to ${customer_email}`);
}

async function handleOrderRefunded(data) {
  console.log(`Refund processed for order ${data.order_id}`);
}

async function handleSubscriptionCreated(data) {
  console.log(`Subscription ${data.plan_name} created`);
}

async function handleSubscriptionCancelled(data) {
  console.log(`Subscription ${data.subscription_id} cancelled`);
}

async function deliverItem(email, item) {
  // Implement your delivery logic
}

app.listen(3000, () => {
  console.log('Webhook server running on port 3000');
});

TypeScript Types

typescript
interface Product {
  id: number;
  name: string;
  description: string;
  price: string;
  type: 'digital' | 'physical' | 'subscription';
  is_active: boolean;
  category?: {
    id: number;
    name: string;
  };
  images: Array<{
    id: number;
    url: string;
    is_primary: boolean;
  }>;
  created_at: string;
  updated_at: string;
}

interface Order {
  id: number;
  order_number: string;
  status: 'pending' | 'processing' | 'completed' | 'cancelled';
  payment_status: 'pending' | 'paid' | 'failed' | 'refunded';
  total: string;
  currency: string;
  customer_email: string;
  customer_name: string;
  items: OrderItem[];
  created_at: string;
  updated_at: string;
}

interface OrderItem {
  id: number;
  product_id: number;
  product_name: string;
  quantity: number;
  price: string;
  total: string;
}

interface WebhookEvent {
  id: string;
  event_type: string;
  created_at: string;
  data: Record<string, unknown>;
}

interface PaginatedResponse<T> {
  success: boolean;
  data: T[];
  meta: {
    current_page: number;
    last_page: number;
    per_page: number;
    total: number;
  };
}

Using with Queue (Bull)

javascript
const Queue = require('bull');
const webhookQueue = new Queue('pixlpay-webhooks');

// In webhook handler
app.post('/webhooks/pixlpay', async (req, res) => {
  // Verify signature...

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

  // Add to queue
  await webhookQueue.add(event, {
    attempts: 3,
    backoff: {
      type: 'exponential',
      delay: 1000,
    },
  });

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

// Process queue
webhookQueue.process(async (job) => {
  const event = job.data;
  await processEvent(event);
});

webhookQueue.on('failed', (job, err) => {
  console.error(`Job ${job.id} failed:`, err);
});

Built for game developers, by game developers.