~/icsd.ir — bash
SYSTEM_ONLINE

🔌 فصل ۹: REST API ووکامرس

REST API ووکامرس به شما اجازه می‌دهد از هر برنامه‌ای (موبایل، ربات تلگرام، اپلیکیشن دسکتاپ، Flutter) با فروشگاه ارتباط برقرار کنید. در این فصل تمام چم و خم آن را یاد می‌گیرید — مخصوصاً برای پروژه‌های Flutter شما.

🔑 تولید کلید API و احراز هویت

تولید کلید از پنل

  1. برو به WooCommerce → Settings → Advanced → REST API
  2. روی Add key کلیک کن
  3. توضیح: نام پروژه (مثل «اپ Flutter Apadana»)
  4. کاربر: یک کاربر با نقش Administrator یا Shop Manager
  5. دسترسی: Read/Write
  6. کلید Consumer Key و Consumer Secret را ذخیره کن
⚠️ امنیت: Consumer Secret فقط یک بار نمایش داده می‌شود. حتماً ذخیره کنید.

تست با curl

# لیست محصولات
curl https://shop.icsd.ir/wp-json/wc/v3/products 
  -u ck_xxxxxxxxxxxx:cs_xxxxxxxxxxxx

# با فیلتر
curl "https://shop.icsd.ir/wp-json/wc/v3/products?per_page=10&category=15" 
  -u ck_xxxxxxxxxxxx:cs_xxxxxxxxxxxx

روش‌های احراز هویت

روش زمان استفاده مثال
Basic Auth HTTPS داخلی، server-to-server -u ck:cs
OAuth 1.0a HTTP بدون SSL (نامعمول) مدیریت پیچیده
JWT اپ موبایل/Flutter (با افزونه) Authorization: Bearer xxx
Application Passwords WP Core (نه WC) کاربر WordPress عادی

📦 API محصولات

Endpointهای پرکاربرد

Method URL توضیح
GET /wc/v3/products لیست محصولات
GET /wc/v3/products/{id} محصول خاص
POST /wc/v3/products ساخت محصول جدید
PUT /wc/v3/products/{id} ویرایش محصول
DELETE /wc/v3/products/{id} حذف محصول
GET /wc/v3/products/categories لیست دسته‌ها
GET /wc/v3/products/{id}/variations تنوع‌های محصول متغیر

پارامترهای فیلتر

GET /wc/v3/products?
  per_page=20            # تعداد در صفحه (max=100)
  &page=2                # صفحه
  &search=فرش            # جستجو
  &category=15,16        # فیلتر دسته‌ها
  &tag=23                # فیلتر تگ
  &status=publish        # وضعیت
  &featured=true         # محصولات ویژه
  &on_sale=true          # تخفیف‌خورده
  &min_price=100000      # حداقل قیمت
  &max_price=500000      # حداکثر قیمت
  &orderby=price         # مرتب‌سازی
  &order=asc             # صعودی/نزولی
  &stock_status=instock  # موجود
  &after=2024-01-01      # بعد از تاریخ

ساخت محصول با POST

curl -X POST https://shop.icsd.ir/wp-json/wc/v3/products 
  -u ck_xxx:cs_xxx 
  -H "Content-Type: application/json" 
  -d '{
    "name": "فرش دستباف ۶ متری",
    "type": "simple",
    "regular_price": "12000000",
    "sale_price": "10000000",
    "description": "فرش دستباف نقشه افشار، رنگ شیری-قهوه‌ای",
    "short_description": "زیبا و بادوام",
    "categories": [{"id": 15}],
    "images": [
      {"src": "https://example.com/carpet1.jpg"},
      {"src": "https://example.com/carpet2.jpg"}
    ],
    "stock_quantity": 1,
    "manage_stock": true,
    "weight": "8",
    "dimensions": {"length": "300", "width": "200", "height": "1"},
    "attributes": [
      {"name": "اندازه", "options": ["۲ × ۳ متر"], "visible": true},
      {"name": "جنس", "options": ["پشم"], "visible": true}
    ],
    "meta_data": [
      {"key": "_origin", "value": "اصفهان"},
      {"key": "_pattern", "value": "افشار"}
    ]
  }'

به‌روزرسانی موجودی Bulk

curl -X POST https://shop.icsd.ir/wp-json/wc/v3/products/batch 
  -u ck_xxx:cs_xxx 
  -H "Content-Type: application/json" 
  -d '{
    "update": [
      {"id": 100, "stock_quantity": 5},
      {"id": 101, "stock_quantity": 3},
      {"id": 102, "stock_quantity": 10, "regular_price": "150000"}
    ]
  }'

📋 API سفارش‌ها

ساخت سفارش

curl -X POST https://shop.icsd.ir/wp-json/wc/v3/orders 
  -u ck_xxx:cs_xxx 
  -H "Content-Type: application/json" 
  -d '{
    "payment_method": "icsd_zarinpal",
    "payment_method_title": "زرین‌پال",
    "set_paid": false,
    "billing": {
      "first_name": "محمدعلی",
      "last_name": "ناظری",
      "address_1": "خیابان امام",
      "city": "کاشان",
      "state": "اصفهان",
      "postcode": "8716743",
      "country": "IR",
      "email": "ali@icsd.ir",
      "phone": "09120000000"
    },
    "shipping": { ... },
    "line_items": [
      {"product_id": 100, "quantity": 2},
      {"product_id": 101, "quantity": 1, "variation_id": 105}
    ],
    "shipping_lines": [
      {"method_id": "tipax", "method_title": "تیپاکس", "total": "50000"}
    ],
    "meta_data": [
      {"key": "_app_source", "value": "Flutter Android"}
    ]
  }'

تغییر وضعیت سفارش

curl -X PUT https://shop.icsd.ir/wp-json/wc/v3/orders/250 
  -u ck_xxx:cs_xxx 
  -d '{"status": "completed"}'

افزودن یادداشت به سفارش

curl -X POST https://shop.icsd.ir/wp-json/wc/v3/orders/250/notes 
  -u ck_xxx:cs_xxx 
  -d '{
    "note": "کد رهگیری: TPX12345",
    "customer_note": true
  }'

👥 API مشتریان

# ساخت مشتری
POST /wc/v3/customers
{
  "email": "user@example.com",
  "first_name": "علی",
  "last_name": "محمدی",
  "username": "ali",
  "password": "Strong!Pass123",
  "billing": {...},
  "shipping": {...}
}

# جستجوی مشتری
GET /wc/v3/customers?email=user@example.com
GET /wc/v3/customers?role=customer&page=1

# سفارش‌های یک مشتری
GET /wc/v3/orders?customer=15

🔧 ساخت Endpoint سفارشی

ووکامرس endpoint های پیش‌فرض دارد، اما گاهی نیاز به منطق سفارشی است:

includes/api/class-icsd-api.php
<?php
defined('ABSPATH') || exit;

class ICSD_Custom_API {
    
    public function __construct() {
        add_action('rest_api_init', [$this, 'register_routes']);
    }
    
    public function register_routes() {
        // GET /wp-json/icsd/v1/featured-products
        register_rest_route('icsd/v1', '/featured-products', [
            'methods'  => 'GET',
            'callback' => [$this, 'get_featured'],
            'permission_callback' => '__return_true', // عمومی
        ]);
        
        // POST /wp-json/icsd/v1/cart/add
        register_rest_route('icsd/v1', '/cart/add', [
            'methods'  => 'POST',
            'callback' => [$this, 'add_to_cart'],
            'permission_callback' => [$this, 'check_jwt_auth'],
            'args' => [
                'product_id' => ['required' => true, 'type' => 'integer'],
                'quantity'   => ['default' => 1, 'type' => 'integer'],
            ],
        ]);
        
        // GET /wp-json/icsd/v1/dashboard
        register_rest_route('icsd/v1', '/dashboard', [
            'methods'  => 'GET',
            'callback' => [$this, 'get_dashboard'],
            'permission_callback' => function() {
                return current_user_can('manage_woocommerce');
            },
        ]);
    }
    
    public function get_featured($request) {
        $args = [
            'post_type'      => 'product',
            'posts_per_page' => 8,
            'meta_query' => [[
                'key'   => '_featured',
                'value' => 'yes',
            ]],
        ];
        
        $query = new WP_Query($args);
        $products = [];
        
        foreach ($query->posts as $post) {
            $product = wc_get_product($post->ID);
            $products[] = [
                'id'       => $product->get_id(),
                'name'     => $product->get_name(),
                'price'    => $product->get_price(),
                'image'    => wp_get_attachment_url($product->get_image_id()),
                'url'      => $product->get_permalink(),
                'on_sale'  => $product->is_on_sale(),
            ];
        }
        
        return rest_ensure_response($products);
    }
    
    public function add_to_cart($request) {
        $product_id = $request->get_param('product_id');
        $quantity = $request->get_param('quantity');
        
        if (!WC()->cart) {
            wc_load_cart();
        }
        
        $key = WC()->cart->add_to_cart($product_id, $quantity);
        
        if (!$key) {
            return new WP_Error('cart_error', 'افزودن به سبد ناموفق', ['status' => 400]);
        }
        
        return rest_ensure_response([
            'success'  => true,
            'cart_key' => $key,
            'cart_total' => WC()->cart->get_cart_contents_count(),
        ]);
    }
    
    public function get_dashboard($request) {
        // آمار سریع برای داشبورد ادمین
        $today_orders = wc_get_orders([
            'date_created' => '>' . strtotime('today'),
            'limit' => -1,
        ]);
        
        $today_revenue = 0;
        foreach ($today_orders as $order) {
            if ($order->get_status() !== 'cancelled') {
                $today_revenue += $order->get_total();
            }
        }
        
        return rest_ensure_response([
            'today_orders'  => count($today_orders),
            'today_revenue' => $today_revenue,
            'low_stock'     => count(wc_get_low_stock_products()),
        ]);
    }
    
    public function check_jwt_auth($request) {
        // نیاز به افزونه JWT Auth یا کد اختصاصی
        $auth_header = $request->get_header('authorization');
        if (!$auth_header || strpos($auth_header, 'Bearer ') !== 0) {
            return false;
        }
        
        $token = substr($auth_header, 7);
        $user_id = $this->validate_jwt($token);
        
        if (!$user_id) return false;
        
        wp_set_current_user($user_id);
        return true;
    }
    
    private function validate_jwt($token) {
        // پیاده‌سازی validation با firebase/php-jwt
        // برمی‌گرداند: user_id یا false
    }
}

new ICSD_Custom_API();

📱 کلاینت Flutter کامل

برای پروژه Apadana شما — یک کلاینت تمیز:

lib/services/woocommerce_api.dart
import 'package:dio/dio.dart';
import 'package:shared_preferences/shared_preferences.dart';

class WooCommerceAPI {
  final Dio _dio;
  final String baseUrl;
  final String consumerKey;
  final String consumerSecret;
  
  WooCommerceAPI({
    required this.baseUrl,
    required this.consumerKey,
    required this.consumerSecret,
  }) : _dio = Dio() {
    _dio.options.baseUrl = '$baseUrl/wp-json/wc/v3';
    _dio.options.connectTimeout = const Duration(seconds: 15);
    _dio.options.receiveTimeout = const Duration(seconds: 15);
    
    _dio.options.headers['Authorization'] = 
      'Basic ' + base64Encode(utf8.encode('$consumerKey:$consumerSecret'));
    
    _dio.interceptors.add(LogInterceptor(responseBody: true));
  }
  
  // محصولات
  Future<List<Product>> getProducts({
    int page = 1,
    int perPage = 20,
    String? search,
    int? categoryId,
  }) async {
    try {
      final response = await _dio.get('/products', queryParameters: {
        'page': page,
        'per_page': perPage,
        if (search != null) 'search': search,
        if (categoryId != null) 'category': categoryId,
      });
      
      return (response.data as List)
          .map((json) => Product.fromJson(json))
          .toList();
    } on DioException catch (e) {
      throw _handleError(e);
    }
  }
  
  Future<Product> getProduct(int id) async {
    final response = await _dio.get('/products/$id');
    return Product.fromJson(response.data);
  }
  
  // سفارش
  Future<Order> createOrder(Map<String, dynamic> orderData) async {
    final response = await _dio.post('/orders', data: orderData);
    return Order.fromJson(response.data);
  }
  
  Future<List<Order>> getCustomerOrders(int customerId) async {
    final response = await _dio.get('/orders', queryParameters: {
      'customer': customerId,
      'per_page': 50,
    });
    return (response.data as List).map((j) => Order.fromJson(j)).toList();
  }
  
  // کاتگوری
  Future<List<Category>> getCategories() async {
    final response = await _dio.get('/products/categories', queryParameters: {
      'per_page': 100,
      'hide_empty': true,
    });
    return (response.data as List).map((j) => Category.fromJson(j)).toList();
  }
  
  Exception _handleError(DioException e) {
    if (e.response != null) {
      final data = e.response!.data;
      final message = data is Map ? (data['message'] ?? 'خطا') : 'خطا';
      return Exception(message);
    }
    return Exception('عدم اتصال به سرور');
  }
}

// مدل
class Product {
  final int id;
  final String name;
  final String price;
  final String? salePrice;
  final List<String> images;
  final bool inStock;
  
  Product({
    required this.id,
    required this.name,
    required this.price,
    this.salePrice,
    required this.images,
    required this.inStock,
  });
  
  factory Product.fromJson(Map<String, dynamic> json) => Product(
    id: json['id'],
    name: json['name'],
    price: json['price'] ?? '',
    salePrice: json['sale_price'],
    images: (json['images'] as List? ?? [])
              .map((img) => img['src'] as String).toList(),
    inStock: json['stock_status'] == 'instock',
  );
}

⚡ بهینه‌سازی پرفورمنس API

۱. کش با Transient

add_filter('woocommerce_rest_prepare_product_object', function($response, $product, $request) {
    $cache_key = 'wc_product_' . $product->get_id() . '_' . md5(serialize($request->get_params()));
    $cached = get_transient($cache_key);
    
    if ($cached) return $cached;
    
    set_transient($cache_key, $response, 5 * MINUTE_IN_SECONDS);
    return $response;
}, 10, 3);

// پاک‌سازی هنگام تغییر
add_action('woocommerce_update_product', function($product_id) {
    global $wpdb;
    $wpdb->query("DELETE FROM {$wpdb->options} WHERE option_name LIKE '_transient_wc_product_{$product_id}_%'");
});

۲. کاهش فیلدهای برگشتی

# فقط فیلدهای مورد نیاز
GET /wc/v3/products?_fields=id,name,price,images

۳. Pagination صحیح

// از header X-WP-Total استفاده کن
final response = await _dio.get('/products');
final total = int.parse(response.headers.value('x-wp-total') ?? '0');
final totalPages = int.parse(response.headers.value('x-wp-totalpages') ?? '0');

🔒 امنیت API

  • همیشه از HTTPS استفاده کنید (SSL واجب است)
  • کلیدها را در کد commit نکنید — از .env استفاده کنید
  • برای کلیدهای موبایل، Read فقط (نه Write)
  • Rate Limit در nginx یا Cloudflare
  • IP Whitelist برای کلیدهای حساس
  • لاگ کردن درخواست‌های مشکوک
  • Rotation منظم کلیدها (هر ۶ ماه)

Rate Limiting در ووکامرس

add_filter('rest_pre_dispatch', function($result, $server, $request) {
    if (strpos($request->get_route(), '/wc/') === false) return $result;
    
    $ip = $_SERVER['REMOTE_ADDR'];
    $key = 'rate_limit_' . md5($ip);
    $count = (int) get_transient($key);
    
    if ($count >= 100) { // ۱۰۰ درخواست در دقیقه
        return new WP_Error('rate_limit', 'محدودیت درخواست', ['status' => 429]);
    }
    
    set_transient($key, $count + 1, MINUTE_IN_SECONDS);
    return $result;
}, 10, 3);

📝 خلاصه فصل

  • تولید کلید REST API و روش‌های احراز هویت
  • API محصولات، سفارش‌ها، مشتریان
  • ساخت Endpoint سفارشی با register_rest_route
  • کلاینت Flutter کامل برای Apadana
  • بهینه‌سازی با کش، فیلد و Pagination
  • امنیت با Rate Limit و HTTPS

نمایش سایت

رنگ سایت
حالت نمایش
اندازهٔ متن
خوانایی

این تنظیمات فقط روی مرورگر شما ذخیره می‌شود.