> For the complete documentation index, see [llms.txt](https://docs.gameket.io/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.gameket.io/examples/node.js.md).

# Node.js

#### Basic Setup

{% code expandable="true" %}

```javascript
class GametekMerchantAPI {
  constructor(apiKey, secret, baseUrl = 'https://api.gameket.io') {
    this.apiKey = apiKey;
    this.secret = secret;
    this.baseUrl = baseUrl;
    this.token = null;
    this.tokenExpiresAt = null;
  }

  async getToken() {
    // Return cached token if still valid
    if (this.token && Date.now() < this.tokenExpiresAt - 60000) {
      return this. token;
    }

    const response = await fetch(`${this.baseUrl}/merchant/auth/check`, {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({
        apiKey: this.apiKey,
        secret: this.secret
      })
    });

    if (!response.ok) throw new Error('Token request failed');

    const { data } = await response.json();
    this.token = data.token;
    this.tokenExpiresAt = new Date(data.expiresAt).getTime();

    return this.token;
  }

  async request(method, endpoint, body = null) {
    const token = await this.getToken();
    const options = {
      method,
      headers: {
        'Authorization': `Bearer ${token}`,
        'x-merchant-api-key': this.apiKey,
        'Content-Type': 'application/json'
      }
    };

    if (body) {
      options.body = JSON.stringify(body);
    }

    const response = await fetch(`${this.baseUrl}${endpoint}`, options);
    const data = await response.json();

    if (!response.ok) {
      throw new Error(`API Error: ${data.error}`);
    }

    return data;
  }

  // Convenience methods
  async getProfile() {
    return this.request('GET', '/merchant');
  }

  async listOrders(page = 1, limit = 20, filters = {}) {
    const params = new URLSearchParams({ page, limit, ...filters });
    return this.request('GET', `/merchant/orders?${params}`);
  }

  async createProduct(productData) {
    return this.request('POST', '/merchant/products', productData);
  }

  async deliverCodes(orderId, codes) {
    return this.request('POST', `/merchant/orders/codes?orderId=${orderId}`, { codes });
  }

  async refundOrder(orderId, reason, amount) {
    return this.request('POST', `/merchant/orders/refund?orderId=${orderId}`, {
      reason,
      amount
    });
  }
}

// Usage
const client = new GametekMerchantAPI(
  process.env.MERCHANT_API_KEY,
  process.env.MERCHANT_SECRET
);

// Get profile
const profile = await client.getProfile();
console.log(`Welcome ${profile.data.storeName}`);

// List pending orders
const orders = await client.listOrders(1, 50, { status: 'pending' });
console.log(`Found ${orders.data.length} pending orders`);
```

{% endcode %}

#### Batch Process Orders

{% code expandable="true" %}

```javascript
async function processPendingOrders(client) {
  let page = 1;
  let hasMore = true;

  while (hasMore) {
    const { data: orders, pagination } = await client.listOrders(page, 100, {
      status: 'pending'
    });

    for (const order of orders) {
      try {
        // Generate or fetch codes from your system
        const codes = await generateCodesForOrder(order);

        // Deliver codes
        await client.deliverCodes(order.orderId, codes);
        console.log(`✓ Delivered codes for order ${order.orderId}`);
      } catch (error) {
        console.error(`✗ Failed to process order ${order.orderId}:`, error);
      }
    }

    hasMore = page < pagination.pages;
    page++;
  }
}

async function generateCodesForOrder(order) {
  // Your code generation logic here
  return [
    `FIFA26-${Math.random().toString(36).substr(2, 9).toUpperCase()}`,
    `FIFA26-${Math.random().toString(36).substr(2, 9).toUpperCase()}`
  ];
}

// Run every hour
setInterval(() => processPendingOrders(client), 60 * 60 * 1000);
```

{% endcode %}
