📋 فصل ۳: سفارشها و فرآیند پرداخت
سفارش (Order) قلب تپنده هر فروشگاه است. در این فصل چرخه کامل سفارش، تسلط بر HPOS، سفارشیسازی Checkout، تعریف وضعیتهای اختصاصی و مدیریت سفارش با کد را میآموزیم.
سفارش (Order) قلب تپنده هر فروشگاه است. در این فصل چرخه کامل سفارش، تسلط بر HPOS، سفارشیسازی Checkout، تعریف وضعیتهای اختصاصی و مدیریت سفارش با کد را میآموزیم.
📑 سرفصلهای این فصل
۱. چرخه حیات سفارش
هر سفارش از مرحله سبد خرید تا تحویل، چندین وضعیت را طی میکند:
سبد خرید
↓
صفحه پرداخت (Checkout)
↓
ثبت سفارش → وضعیت: pending (در انتظار پرداخت)
↓
هدایت به درگاه پرداخت
↓ (پرداخت موفق)
وضعیت: processing (در حال پردازش)
↓ (آمادهسازی و ارسال)
وضعیت: completed (تکمیل شده)
مسیرهای دیگر:
- failed (ناموفق) → پرداخت رد شد
- cancelled (لغو شده)
- refunded (مسترد شده)
- on-hold (در انتظار)
۲. وضعیتهای سفارش
| وضعیت | Slug | توضیح |
|---|---|---|
| در انتظار پرداخت | wc-pending |
پرداخت شروع نشده |
| در حال پردازش | wc-processing |
پرداخت موفق – آمادهسازی |
| در انتظار | wc-on-hold |
منتظر بررسی (مثلاً کارت به کارت) |
| تکمیل شده | wc-completed |
تحویل شده |
| لغو شده | wc-cancelled |
توسط مشتری/مدیر لغو |
| مسترد شده | wc-refunded |
مبلغ بازگشت داده شد |
| ناموفق | wc-failed |
پرداخت رد شد |
| پیشنویس بررسی | wc-checkout-draft |
قبل از ثبت نهایی |
wc- ذخیره میشوند، اما در توابع و فیلترها بدون پیشوند: مثلاً $order->update_status('processing') ولی در دیتابیس wc-processing.
۳. کلاس WC_Order در عمق
متدهای پرکاربرد
$order = wc_get_order( $order_id );
// اطلاعات کلی
$order->get_id(); // شناسه سفارش
$order->get_order_number(); // شماره سفارش (میتواند با ID متفاوت باشد)
$order->get_status(); // وضعیت بدون wc-
$order->get_currency(); // ارز
$order->get_total(); // مجموع
$order->get_subtotal(); // جمع آیتمها
$order->get_total_tax(); // مالیات
$order->get_shipping_total(); // هزینه ارسال
$order->get_total_discount(); // تخفیف
$order->get_payment_method(); // slug درگاه
$order->get_payment_method_title(); // نام درگاه
$order->get_transaction_id(); // ID تراکنش از درگاه
// مشتری
$order->get_customer_id(); // 0 اگر مهمان باشد
$order->get_user(); // WP_User
$order->get_billing_email();
$order->get_billing_phone();
$order->get_billing_first_name();
$order->get_billing_last_name();
$order->get_billing_address_1();
$order->get_billing_city();
$order->get_billing_state();
$order->get_billing_postcode();
$order->get_billing_country();
// آدرس ارسال (ممکن است متفاوت با صورتحساب باشد)
$order->get_shipping_first_name();
$order->get_shipping_address_1();
// ...
// آیتمهای سفارش
foreach ( $order->get_items() as $item_id => $item ) {
$item_name = $item->get_name();
$product_id = $item->get_product_id();
$variation_id = $item->get_variation_id();
$quantity = $item->get_quantity();
$subtotal = $item->get_subtotal();
$total = $item->get_total();
$product = $item->get_product();
$meta_data = $item->get_meta_data();
}
// آیتمهای ارسال و کوپن
foreach ( $order->get_items( 'shipping' ) as $shipping_item ) { /* ... */ }
foreach ( $order->get_items( 'fee' ) as $fee_item ) { /* ... */ }
foreach ( $order->get_items( 'coupon' ) as $coupon_item ) { /* ... */ }
// بهروزرسانی وضعیت با یادداشت
$order->update_status( 'completed', 'سفارش با موفقیت تحویل داده شد.' );
// افزودن یادداشت داخلی (فقط ادمین میبیند)
$order->add_order_note( 'یادداشت داخلی: انبار شماره ۲', false );
// افزودن یادداشت برای مشتری (ایمیل میشود)
$order->add_order_note( 'بسته شما در حال ارسال است.', 1 );
// متادیتای سفارش
$order->update_meta_data( '_my_custom_field', 'value' );
$value = $order->get_meta( '_my_custom_field' );
$order->save();
جستجو و فیلتر سفارشها
// روش پیشنهادی (سازگار با HPOS)
$orders = wc_get_orders( [
'status' => [ 'processing', 'completed' ],
'limit' => 50,
'orderby' => 'date',
'order' => 'DESC',
'date_created' => '>=' . strtotime( '-30 days' ),
'customer_id' => 0, // فقط مهمانها
] );
foreach ( $orders as $order ) {
echo $order->get_id() . ' - ' . $order->get_total() . PHP_EOL;
}
// فیلتر بر اساس آیتم خاص
$orders = wc_get_orders( [
'limit' => -1,
'meta_key' => '_payment_method',
'meta_value' => 'zarinpal',
] );
// سفارشهای یک مشتری خاص
$customer_orders = wc_get_orders( [
'customer_id' => 123,
'limit' => -1,
] );
ساخت سفارش با کد (برای فروش حضوری)
function icsd_create_manual_order( $items, $customer_data ) {
// ساخت سفارش
$order = wc_create_order();
// افزودن محصولات
foreach ( $items as $item ) {
$product = wc_get_product( $item['product_id'] );
$order->add_product( $product, $item['quantity'] );
}
// آدرس صورتحساب
$order->set_address( [
'first_name' => $customer_data['first_name'],
'last_name' => $customer_data['last_name'],
'phone' => $customer_data['phone'],
'email' => $customer_data['email'],
'address_1' => $customer_data['address'],
'city' => $customer_data['city'],
'state' => $customer_data['state'],
'postcode' => $customer_data['postcode'],
'country' => 'IR',
], 'billing' );
// اگر مشتری ثبتنام کرده
if ( ! empty( $customer_data['user_id'] ) ) {
$order->set_customer_id( $customer_data['user_id'] );
}
// افزودن هزینه ارسال
$shipping_item = new WC_Order_Item_Shipping();
$shipping_item->set_method_title( 'پست پیشتاز' );
$shipping_item->set_method_id( 'flat_rate' );
$shipping_item->set_total( 50000 );
$order->add_item( $shipping_item );
// محاسبه مجموع
$order->calculate_totals();
// وضعیت اولیه
$order->set_status( 'processing', 'سفارش حضوری توسط ادمین ثبت شد.' );
// متادیتای اختصاصی
$order->update_meta_data( '_icsd_order_source', 'in_store' );
$order->update_meta_data( '_icsd_seller', get_current_user_id() );
// ذخیره
$order_id = $order->save();
return $order_id;
}
۴. ساخت وضعیت سفارش سفارشی
گاهی نیاز به وضعیتهای جدیدی داریم. مثلاً برای فرشهای سفارشی: «در حال طراحی»، «در حال بافت»، «آماده تحویل».
<?php
class ICSD_Custom_Order_Statuses {
public function __construct() {
add_action( 'init', [ $this, 'register_statuses' ] );
add_filter( 'wc_order_statuses', [ $this, 'add_to_list' ] );
add_filter( 'bulk_actions-edit-shop_order', [ $this, 'add_bulk_actions' ] );
}
public function register_statuses() {
register_post_status( 'wc-designing', [
'label' => 'در حال طراحی',
'public' => true,
'show_in_admin_status_list' => true,
'show_in_admin_all_list' => true,
'exclude_from_search' => false,
'label_count' => _n_noop(
'در حال طراحی (%s)',
'در حال طراحی (%s)'
),
] );
register_post_status( 'wc-weaving', [
'label' => 'در حال بافت',
'public' => true,
'show_in_admin_status_list' => true,
'show_in_admin_all_list' => true,
'exclude_from_search' => false,
'label_count' => _n_noop(
'در حال بافت (%s)',
'در حال بافت (%s)'
),
] );
register_post_status( 'wc-ready-to-ship', [
'label' => 'آماده ارسال',
'public' => true,
'show_in_admin_status_list' => true,
'show_in_admin_all_list' => true,
'exclude_from_search' => false,
'label_count' => _n_noop(
'آماده ارسال (%s)',
'آماده ارسال (%s)'
),
] );
}
public function add_to_list( $order_statuses ) {
$new_statuses = [];
foreach ( $order_statuses as $key => $status ) {
$new_statuses[ $key ] = $status;
// بعد از processing، وضعیتهای ما را اضافه کن
if ( 'wc-processing' === $key ) {
$new_statuses['wc-designing'] = 'در حال طراحی';
$new_statuses['wc-weaving'] = 'در حال بافت';
$new_statuses['wc-ready-to-ship'] = 'آماده ارسال';
}
}
return $new_statuses;
}
public function add_bulk_actions( $actions ) {
$actions['mark_designing'] = 'تغییر به: در حال طراحی';
$actions['mark_weaving'] = 'تغییر به: در حال بافت';
$actions['mark_ready-to-ship'] = 'تغییر به: آماده ارسال';
return $actions;
}
}
new ICSD_Custom_Order_Statuses();
نمایش وضعیتها در پنل مشتری
// رنگبندی وضعیتهای سفارشی در پیشخوان
add_action( 'admin_head', 'icsd_custom_status_colors' );
function icsd_custom_status_colors() {
?>
۵. سفارشیسازی Checkout
تغییر ترتیب فیلدها
add_filter( 'woocommerce_checkout_fields', 'icsd_reorder_checkout_fields' );
function icsd_reorder_checkout_fields( $fields ) {
// تغییر اولویت
$fields['billing']['billing_phone']['priority'] = 25;
$fields['billing']['billing_email']['priority'] = 26;
// اجباری کردن کد ملی
$fields['billing']['billing_national_id'] = [
'type' => 'text',
'label' => 'کد ملی',
'placeholder' => 'مثال: 0012345678',
'required' => true,
'class' => [ 'form-row-wide' ],
'priority' => 27,
];
// تغییر برچسب
$fields['billing']['billing_address_1']['label'] = 'آدرس کامل پستی';
$fields['billing']['billing_postcode']['label'] = 'کد پستی ۱۰ رقمی';
// حذف فیلدهای غیر ضروری برای ایران
unset( $fields['billing']['billing_company'] );
unset( $fields['billing']['billing_address_2'] );
return $fields;
}
اعتبارسنجی کد ملی ایرانی
add_action( 'woocommerce_checkout_process', 'icsd_validate_national_id' );
function icsd_validate_national_id() {
$national_id = $_POST['billing_national_id'] ?? '';
if ( empty( $national_id ) ) {
wc_add_notice( 'لطفاً کد ملی خود را وارد کنید.', 'error' );
return;
}
if ( ! icsd_is_valid_national_id( $national_id ) ) {
wc_add_notice( 'کد ملی وارد شده معتبر نیست.', 'error' );
}
}
function icsd_is_valid_national_id( $code ) {
$code = preg_replace( '/[^0-9]/', '', $code );
if ( strlen( $code ) !== 10 ) return false;
if ( preg_match( '/(d)1{9}/', $code ) ) return false; // اعداد تکراری
$sum = 0;
for ( $i = 0; $i < 9; $i++ ) {
$sum += $code[ $i ] * ( 10 - $i );
}
$remainder = $sum % 11;
$check_digit = (int) $code[9];
if ( $remainder < 2 ) {
return $check_digit === $remainder;
}
return $check_digit === ( 11 - $remainder );
}
// ذخیره در سفارش
add_action( 'woocommerce_checkout_update_order_meta', 'icsd_save_national_id' );
function icsd_save_national_id( $order_id ) {
if ( ! empty( $_POST['billing_national_id'] ) ) {
update_post_meta( $order_id, '_billing_national_id', sanitize_text_field( $_POST['billing_national_id'] ) );
}
}
// نمایش در پیشخوان سفارش
add_action( 'woocommerce_admin_order_data_after_billing_address', 'icsd_show_national_id' );
function icsd_show_national_id( $order ) {
$national_id = get_post_meta( $order->get_id(), '_billing_national_id', true );
if ( $national_id ) {
echo 'کد ملی: ' . esc_html( $national_id ) . '
';
}
}
۶. فیلدهای سفارشی Checkout
افزودن فیلد در بخشهای مختلف
// روش ۱: افزودن قبل از فرم پرداخت
add_action( 'woocommerce_review_order_before_payment', 'icsd_add_gift_field' );
function icsd_add_gift_field() {
woocommerce_form_field( 'icsd_is_gift', [
'type' => 'checkbox',
'label' => 'این سفارش هدیه است',
'class' => [ 'icsd-gift-toggle' ],
] );
woocommerce_form_field( 'icsd_gift_message', [
'type' => 'textarea',
'label' => 'متن کارت هدیه',
'placeholder' => 'پیام شما...',
'required' => false,
'class' => [ 'icsd-gift-message' ],
] );
}
// ذخیره در سفارش
add_action( 'woocommerce_checkout_update_order_meta', 'icsd_save_gift_data' );
function icsd_save_gift_data( $order_id ) {
if ( ! empty( $_POST['icsd_is_gift'] ) ) {
update_post_meta( $order_id, '_icsd_is_gift', '1' );
update_post_meta( $order_id, '_icsd_gift_message', sanitize_textarea_field( $_POST['icsd_gift_message'] ?? '' ) );
}
}
// نمایش در سفارش (پیشخوان و ایمیل)
add_action( 'woocommerce_admin_order_data_after_order_details', 'icsd_show_gift_admin' );
function icsd_show_gift_admin( $order ) {
if ( get_post_meta( $order->get_id(), '_icsd_is_gift', true ) ) {
$message = get_post_meta( $order->get_id(), '_icsd_gift_message', true );
echo '🎁 سفارش هدیه:
';
echo '';
echo nl2br( esc_html( $message ) );
echo '
';
}
}
۷. سفارشیسازی ایمیل سفارش
ووکامرس چه ایمیلهایی میفرستد؟
| کلید | گیرنده | زمان ارسال |
|---|---|---|
new_order |
ادمین | سفارش جدید ثبت شد |
cancelled_order |
ادمین | سفارش لغو شد |
failed_order |
ادمین | پرداخت ناموفق |
customer_on_hold_order |
مشتری | سفارش در انتظار |
customer_processing_order |
مشتری | سفارش در حال پردازش |
customer_completed_order |
مشتری | سفارش تکمیل شد |
customer_refunded_order |
مشتری | سفارش مسترد شد |
customer_invoice |
مشتری | دستی توسط ادمین |
customer_note |
مشتری | یادداشت جدید |
customer_new_account |
مشتری | ثبتنام |
سفارشیسازی قالب ایمیل
1. به مسیر افزونه ووکامرس بروید:
wp-content/plugins/woocommerce/templates/emails/
2. فایل مورد نظر را کپی کنید (مثلاً customer-completed-order.php)
3. در قالب فعال خود، این مسیر را بسازید:
wp-content/themes/your-theme/woocommerce/emails/
4. فایل را در آنجا پیست کنید و ویرایش کنید.
ووکامرس به صورت خودکار قالب شما را به جای پیشفرض استفاده میکند.
افزودن محتوای دینامیک به ایمیل
// افزودن کد رهگیری به ایمیل completed
add_action( 'woocommerce_email_after_order_table', 'icsd_add_tracking_to_email', 10, 4 );
function icsd_add_tracking_to_email( $order, $sent_to_admin, $plain_text, $email ) {
if ( $email->id !== 'customer_completed_order' ) return;
$tracking = $order->get_meta( '_icsd_tracking_code' );
if ( ! $tracking ) return;
if ( $plain_text ) {
echo "nnکد رهگیری پست: $trackingn";
echo "لینک رهگیری: https://tracking.post.ir/?id=$trackingn";
} else {
?>
۸. عملیات روی سفارش
بازپرداخت با کد
function icsd_refund_order( $order_id, $amount = null, $reason = '' ) {
$order = wc_get_order( $order_id );
if ( ! $order ) {
return new WP_Error( 'invalid_order', 'سفارش پیدا نشد' );
}
$amount = $amount ?: $order->get_total();
$refund = wc_create_refund( [
'amount' => $amount,
'reason' => $reason,
'order_id' => $order_id,
'refund_payment' => false, // اگر API درگاه را داریم true کنید
] );
if ( is_wp_error( $refund ) ) {
return $refund;
}
$order->update_status( 'refunded', 'بازپرداخت توسط سیستم: ' . $reason );
return $refund;
}
// مثال
icsd_refund_order( 123, 500000, 'مشتری راضی نبود' );
چاپ فاکتور با کد سفارش
function icsd_generate_invoice( $order_id ) {
$order = wc_get_order( $order_id );
if ( ! $order ) return;
ob_start();
?>
فاکتور #get_order_number(); ?>
شماره: get_order_number(); ?>
تاریخ: get_date_created()->getTimestamp() ); ?>
اطلاعات مشتری
نام: get_formatted_billing_full_name(); ?>
تلفن: get_billing_phone(); ?>
آدرس: get_formatted_billing_address(); ?>
محصول
تعداد
قیمت واحد
مجموع
get_items() as $item ): ?>
get_name() ); ?>
get_quantity(); ?>
get_subtotal() / $item->get_quantity() ); ?>
get_total() ); ?>
جمع جزء
get_subtotal() ); ?>
هزینه ارسال
get_shipping_total() ); ?>
مالیات
get_total_tax() ); ?>
مبلغ نهایی
get_total() ); ?>
با تشکر از خرید شما
ICSD - icsd.ir