# Serial Numbers API Import - User Guide

## Overview

The **Serial Numbers API Import** plugin allows you to programmatically import serial numbers/license keys into your WooCommerce store via API. This plugin provides two API endpoints:

1. **WordPress REST API** (Recommended) - Modern, secure, and follows WordPress standards
2. **Legacy WooCommerce API** (Deprecated) - Maintained for backward compatibility

### Performance

✅ **Optimized and verified** for importing **3,000+ keys in under 20 seconds**  
⚡ **Recommended**: 1,000 keys per request for optimal performance

---

## Table of Contents

1. [Installation & Configuration](#installation--configuration)
2. [API Authentication](#api-authentication)
3. [WordPress REST API (Recommended)](#wordpress-rest-api-recommended)
4. [Legacy WooCommerce API (Deprecated)](#legacy-woocommerce-api-deprecated)
5. [API Parameters Reference](#api-parameters-reference)
6. [Response Format](#response-format)
7. [Error Handling](#error-handling)
8. [Code Examples](#code-examples)

---

## Installation & Configuration

### Step 1: Install the Plugin

1. Upload the `serial-numbers-api-import` folder to `/wp-content/plugins/`
2. Activate the plugin through the WordPress admin panel

### Step 2: Configure API Token

1. Navigate to **WooCommerce → Settings → Serial Numbers → General**
2. Scroll down to **Api Import Settings**
3. Enter your desired **Api Import Token** (e.g., `your-secure-token-here`)
4. Click **Save Changes**

> ⚠️ **Security Note**: Use a strong, unique token. This token authenticates all API requests.

---

## API Authentication

All API requests require a valid `token` parameter that matches the token configured in the plugin settings.

---

## WordPress REST API (Recommended)

### Endpoint

```
POST https://your-domain.com/wp-json/serial-numbers-api/v1/import
```

### Request Format

- **Method**: POST
- **Content-Type**: `application/json`
- **Authentication**: Token passed as query parameter or in JSON body

### cURL Example - Using Product ID

```bash
curl -X POST "https://your-domain.com/wp-json/serial-numbers-api/v1/import?token=your-secure-token-here&product_id=123" \
  -H "Content-Type: application/json" \
  -d '{
    "keys": [
      "SERIAL-KEY-001",
      "SERIAL-KEY-002",
      "SERIAL-KEY-003"
    ]
  }'
```

### cURL Example - Using Product SKU

```bash
curl -X POST "https://your-domain.com/wp-json/serial-numbers-api/v1/import?token=your-secure-token-here&product_sku=YOUR-PRODUCT-SKU" \
  -H "Content-Type: application/json" \
  -d '{
    "keys": [
      "SERIAL-KEY-001",
      "SERIAL-KEY-002",
      "SERIAL-KEY-003"
    ]
  }'
```

### cURL Example - With All Optional Parameters

```bash
curl -X POST "https://your-domain.com/wp-json/serial-numbers-api/v1/import?token=your-secure-token-here" \
  -H "Content-Type: application/json" \
  -d '{
    "keys": [
      "SERIAL-KEY-001",
      "SERIAL-KEY-002",
      "SERIAL-KEY-003"
    ],
    "product_id": 123,
    "activation_limit": 5,
    "status": "available",
    "validity": 365
  }'
```

### PHP cURL Example - REST API

```php
<?php

$api_url = 'https://your-domain.com/wp-json/serial-numbers-api/v1/import';
$token   = 'your-secure-token-here';

// Build the endpoint URL with token and product identifier.
$endpoint = $api_url . '?' . http_build_query( array(
    'token'      => $token,
    'product_id' => 123,  // Use product_id OR product_sku, not both.
    // 'product_sku' => 'YOUR-PRODUCT-SKU',  // Alternative: use SKU instead of ID.
) );

// Prepare the serial keys as JSON.
$json_data = json_encode( array(
    'keys' => array(
        'SERIAL-KEY-001',
        'SERIAL-KEY-002',
        'SERIAL-KEY-003',
    ),
) );

// Initialize cURL.
$ch = curl_init();

curl_setopt( $ch, CURLOPT_URL, $endpoint );
curl_setopt( $ch, CURLOPT_POST, true );
curl_setopt( $ch, CURLOPT_POSTFIELDS, $json_data );
curl_setopt( $ch, CURLOPT_RETURNTRANSFER, true );
curl_setopt( $ch, CURLOPT_HTTPHEADER, array(
    'Content-Type: application/json',
    'Content-Length: ' . strlen( $json_data ),
) );

// For local/development environments with self-signed SSL certificates.
// Remove these lines in production.
curl_setopt( $ch, CURLOPT_SSL_VERIFYHOST, 0 );
curl_setopt( $ch, CURLOPT_SSL_VERIFYPEER, 0 );

// Execute the request.
$response = curl_exec( $ch );

// Check for errors.
if ( curl_errno( $ch ) ) {
    echo 'cURL Error: ' . curl_error( $ch );
} else {
    // Decode and display the response.
    $result = json_decode( $response, true );
    echo '<pre>' . print_r( $result, true ) . '</pre>';
}

curl_close( $ch );
```

---

## Legacy WooCommerce API (Deprecated)

> ⚠️ **Deprecation Notice**: This API endpoint is maintained for backward compatibility only. New implementations should use the WordPress REST API.

### Endpoint

```
POST https://your-domain.com/?wc-api=serial-numbers-import-api
```

### Request Format

- **Method**: POST
- **Content-Type**: `application/x-www-form-urlencoded`
- **Data Format**: URL-encoded form data

### cURL Example - Using Product ID

```bash
curl -X POST "https://your-domain.com/?wc-api=serial-numbers-import-api" \
  -d "token=your-secure-token-here" \
  -d "product_id=123" \
  -d "keys[0]=SERIAL-KEY-001" \
  -d "keys[1]=SERIAL-KEY-002" \
  -d "keys[2]=SERIAL-KEY-003"
```

### cURL Example - Using Product SKU

```bash
curl -X POST "https://your-domain.com/?wc-api=serial-numbers-import-api" \
  -d "token=your-secure-token-here" \
  -d "product_sku=YOUR-PRODUCT-SKU" \
  -d "keys[0]=SERIAL-KEY-001" \
  -d "keys[1]=SERIAL-KEY-002" \
  -d "keys[2]=SERIAL-KEY-003"
```

### PHP cURL Example - Legacy API

```php
<?php

$api_url = 'https://your-domain.com/?wc-api=serial-numbers-import-api';
$token   = 'your-secure-token-here';

// Prepare the data array.
$data = array(
    'token'      => $token,
    'product_id' => 123,  // Use product_id OR product_sku, not both.
    // 'product_sku' => 'YOUR-PRODUCT-SKU',  // Alternative: use SKU instead of ID.
    'keys'       => array(
        'SERIAL-KEY-001',
        'SERIAL-KEY-002',
        'SERIAL-KEY-003',
    ),
);

// Convert to URL-encoded string.
$field_string = http_build_query( $data );

// Initialize cURL.
$ch = curl_init();

curl_setopt( $ch, CURLOPT_URL, $api_url );
curl_setopt( $ch, CURLOPT_POST, true );
curl_setopt( $ch, CURLOPT_POSTFIELDS, $field_string );
curl_setopt( $ch, CURLOPT_RETURNTRANSFER, true );

// For local/development environments with self-signed SSL certificates.
// Remove these lines in production.
curl_setopt( $ch, CURLOPT_SSL_VERIFYHOST, 0 );
curl_setopt( $ch, CURLOPT_SSL_VERIFYPEER, 0 );

// Execute the request.
$response = curl_exec( $ch );

// Check for errors.
if ( curl_errno( $ch ) ) {
    echo 'cURL Error: ' . curl_error( $ch );
} else {
    // Decode and display the response.
    $result = json_decode( $response, true );
    echo '<pre>' . print_r( $result, true ) . '</pre>';
}

curl_close( $ch );
```

---

## API Parameters Reference

### Required Parameters

| Parameter | Type | Description |
|-----------|------|-------------|
| `token` | string | API authentication token (configured in plugin settings) |
| `keys` | array | Array of serial keys/license codes to import |
| `product_id` | integer | WooCommerce Product ID **OR** |
| `product_sku` | string | WooCommerce Product SKU (overrides `product_id` if both provided) |

### Optional Parameters

| Parameter | Type | Default | Description |
|-----------|------|---------|-------------|
| `activation_limit` | integer | `0` | Maximum activations allowed per key (0 = unlimited) |
| `status` | string | `available` | Key status: `available`, `sold`, `expired`, `cancelled`, `inactive` |
| `validity` | integer | `0` | Validity period in days (0 = never expires) |
| `order_id` | integer | `0` | Associated WooCommerce Order ID |
| `order_item_id` | integer | `0` | Associated Order Item ID |
| `order_date` | string | `0000-00-00 00:00:00` | Order date in `Y-m-d H:i:s` format |
| `created_date` | string | Current time | Creation date in `Y-m-d H:i:s` format |

### Product Identification

You can identify the product using **either**:

- **`product_id`**: The numeric WooCommerce product ID (works for simple products and variations)
- **`product_sku`**: The product SKU (Stock Keeping Unit)

> 💡 **Tip**: If both `product_id` and `product_sku` are provided, `product_sku` takes priority.

---

## Response Format

### Success Response

```json
{
    "success": true,
    "data": {
        "imported": true,
        "product_id": 123,
        "product": "Product Name",
        "total_imported": 3,
        "total_failed": 0,
        "failed_keys": [],
        "stock_quantity": 103,
        "message": "3 Keys Imported. Total available serial numbers count for this product is 103",
        "timestamp": 1768717255
    }
}
```

### Partial Success Response (Some Keys Failed)

```json
{
    "success": true,
    "data": {
        "imported": true,
        "product_id": 123,
        "product": "Product Name",
        "total_imported": 2,
        "total_failed": 1,
        "failed_keys": [
            {
                "key": "SERIAL-KEY-003",
                "error": "Serial key already exists. Duplicate serial keys are not allowed."
            }
        ],
        "stock_quantity": 102,
        "message": "2 Keys Imported. Total available serial numbers count for this product is 102",
        "timestamp": 1768717255
    }
}
```

### Error Response

```json
{
    "success": false,
    "data": {
        "error": "Invalid product id",
        "code": 403,
        "timestamp": 1768717255
    }
}
```

---

## Error Handling

### Common Error Codes

| Error Message | Cause | Solution |
|---------------|-------|----------|
| `Token is required` | Missing `token` parameter | Include the `token` parameter in your request |
| `Token is not matched` | Invalid token | Verify the token matches the one in plugin settings |
| `Serial keys are required and must be an array` | Missing or invalid `keys` | Provide keys as an array |
| `Product id is required` | Missing product identifier | Provide either `product_id` or `product_sku` |
| `Invalid product id` | Product doesn't exist | Verify the product ID exists in WooCommerce |
| `Invalid product SKU provided` | SKU not found | Verify the SKU exists in WooCommerce |
| `Serial key already exists` | Duplicate key | Each serial key must be unique |

---

## Code Examples

### Bulk Import Class (PHP)

Here's a complete example class for bulk importing serial numbers:

```php
<?php
/**
 * Serial Numbers Bulk Importer Class
 *
 * Example usage:
 *   $importer = new SerialNumberImporter( 'https://your-domain.com', 'your-token' );
 *   $result = $importer->import( 123, $serial_keys_array );
 */
class SerialNumberImporter {

    private $domain;
    private $token;
    private $use_rest_api = true;  // Set to false to use legacy API.
    private $max_keys_per_request = 1000;

    /**
     * Constructor.
     *
     * @param string $domain Your WordPress site domain (e.g., 'https://your-domain.com').
     * @param string $token  API authentication token.
     */
    public function __construct( $domain, $token ) {
        $this->domain = rtrim( $domain, '/' );
        $this->token  = $token;
    }

    /**
     * Import serial keys for a product.
     *
     * @param int|string $product     Product ID or SKU.
     * @param array      $keys        Array of serial keys to import.
     * @param array      $options     Optional parameters (activation_limit, status, validity, etc.).
     * @param bool       $is_sku      Set to true if $product is a SKU instead of ID.
     *
     * @return array Array of results for each batch.
     */
    public function import( $product, array $keys, array $options = array(), $is_sku = false ) {
        $results = array();

        // Split keys into batches for optimal performance.
        $batches = array_chunk( $keys, $this->max_keys_per_request );

        foreach ( $batches as $batch_index => $batch_keys ) {
            echo "Processing batch " . ( $batch_index + 1 ) . " of " . count( $batches ) . "...\n";

            if ( $this->use_rest_api ) {
                $result = $this->import_via_rest_api( $product, $batch_keys, $options, $is_sku );
            } else {
                $result = $this->import_via_legacy_api( $product, $batch_keys, $options, $is_sku );
            }

            $results[] = $result;
        }

        return $results;
    }

    /**
     * Import via WordPress REST API (Recommended).
     */
    private function import_via_rest_api( $product, array $keys, array $options, $is_sku ) {
        $endpoint = $this->domain . '/wp-json/serial-numbers-api/v1/import';

        // Build query parameters.
        $query_params = array( 'token' => $this->token );

        if ( $is_sku ) {
            $query_params['product_sku'] = $product;
        } else {
            $query_params['product_id'] = $product;
        }

        // Add optional parameters to query string.
        foreach ( array( 'activation_limit', 'status', 'validity' ) as $key ) {
            if ( isset( $options[ $key ] ) ) {
                $query_params[ $key ] = $options[ $key ];
            }
        }

        $url = $endpoint . '?' . http_build_query( $query_params );

        // JSON body contains only the keys.
        $json_body = json_encode( array( 'keys' => $keys ) );

        return $this->execute_curl_request( $url, $json_body, true );
    }

    /**
     * Import via Legacy WooCommerce API (Deprecated).
     */
    private function import_via_legacy_api( $product, array $keys, array $options, $is_sku ) {
        $endpoint = $this->domain . '/?wc-api=serial-numbers-import-api';

        // Build form data.
        $data = array(
            'token' => $this->token,
            'keys'  => $keys,
        );

        if ( $is_sku ) {
            $data['product_sku'] = $product;
        } else {
            $data['product_id'] = $product;
        }

        // Add optional parameters.
        foreach ( array( 'activation_limit', 'status', 'validity' ) as $key ) {
            if ( isset( $options[ $key ] ) ) {
                $data[ $key ] = $options[ $key ];
            }
        }

        $field_string = http_build_query( $data );

        return $this->execute_curl_request( $endpoint, $field_string, false );
    }

    /**
     * Execute cURL request.
     *
     * @param string $url      The endpoint URL.
     * @param string $body     Request body (JSON string or URL-encoded string).
     * @param bool   $is_json  Whether the body is JSON.
     *
     * @return array|string Decoded response or error message.
     */
    private function execute_curl_request( $url, $body, $is_json = false ) {
        $ch = curl_init();

        curl_setopt( $ch, CURLOPT_URL, $url );
        curl_setopt( $ch, CURLOPT_POST, true );
        curl_setopt( $ch, CURLOPT_POSTFIELDS, $body );
        curl_setopt( $ch, CURLOPT_RETURNTRANSFER, true );
        curl_setopt( $ch, CURLOPT_TIMEOUT, 60 );

        if ( $is_json ) {
            curl_setopt( $ch, CURLOPT_HTTPHEADER, array(
                'Content-Type: application/json',
                'Content-Length: ' . strlen( $body ),
            ) );
        }

        // SSL options - enable verification in production!
        curl_setopt( $ch, CURLOPT_SSL_VERIFYHOST, 2 );
        curl_setopt( $ch, CURLOPT_SSL_VERIFYPEER, true );

        $response = curl_exec( $ch );

        if ( curl_errno( $ch ) ) {
            $error = 'cURL Error: ' . curl_error( $ch );
            curl_close( $ch );
            return array( 'success' => false, 'error' => $error );
        }

        curl_close( $ch );

        return json_decode( $response, true );
    }

    /**
     * Set whether to use REST API or Legacy API.
     *
     * @param bool $use_rest Set to true for REST API, false for Legacy API.
     */
    public function set_use_rest_api( $use_rest ) {
        $this->use_rest_api = $use_rest;
    }

    /**
     * Set maximum keys per request.
     *
     * @param int $max Maximum keys (recommended: 1000).
     */
    public function set_max_keys_per_request( $max ) {
        $this->max_keys_per_request = $max;
    }
}

// Example Usage:
// ============================================

$importer = new SerialNumberImporter(
    'https://your-domain.com',
    'your-secure-token-here'
);

// Generate some serial keys.
$serial_keys = array();
for ( $i = 1; $i <= 100; $i++ ) {
    $serial_keys[] = 'LICENSE-' . str_pad( $i, 6, '0', STR_PAD_LEFT );
}

// Import using Product ID.
$results = $importer->import( 123, $serial_keys, array(
    'activation_limit' => 5,
    'status'           => 'available',
    'validity'         => 365,
) );

// Or import using Product SKU.
$results = $importer->import( 'YOUR-PRODUCT-SKU', $serial_keys, array(
    'activation_limit' => 5,
), true );  // true = $product is a SKU

print_r( $results );
```

---

## Best Practices

1. **Use the REST API** for new implementations - it's more secure and follows modern standards.

2. **Batch your requests** - Import 1,000 keys per request for optimal performance.

3. **Handle duplicates** - The API rejects duplicate serial keys. Check `failed_keys` in the response.

4. **Secure your token** - Keep your API token confidential and use HTTPS.

5. **Enable SSL verification** in production - Only disable SSL verification for local development.

6. **Monitor responses** - Always check `total_imported` and `total_failed` to ensure all keys were processed.

---

## Support

For issues or questions, please contact the plugin developer or visit the support documentation.

---

**Plugin Version**: 1.1.0  
**Last Updated**: January 2026  
**Compatibility**: WordPress 6.0+, WooCommerce 8.0+, PHP 7.4+
