diff --git a/03_WordPress/wp-content/plugins/ifnex-bridge/assets/css/ifnex-orders.css b/03_WordPress/wp-content/plugins/ifnex-bridge/assets/css/ifnex-orders.css
index 52af5ff..60c6797 100644
--- a/03_WordPress/wp-content/plugins/ifnex-bridge/assets/css/ifnex-orders.css
+++ b/03_WordPress/wp-content/plugins/ifnex-bridge/assets/css/ifnex-orders.css
@@ -211,4 +211,163 @@
max-width: 500px; margin: 40px auto; text-align: center;
background: #fff; padding: 40px; border-radius: var(--ifnex-radius);
border: 1px solid var(--ifnex-border);
+}
+
+/* ═══ Payment Page ═══ */
+.ifnex-payment-page { max-width: 700px; margin: 0 auto; padding: 20px 0; }
+.ifnex-payment-card {
+ background: #fff; border-radius: var(--ifnex-radius);
+ border: 1px solid var(--ifnex-border); padding: 32px;
+}
+.ifnex-payment-card h2 { margin-top: 0; color: var(--ifnex-dark); }
+
+.ifnex-order-summary {
+ background: var(--ifnex-light); border-radius: 8px;
+ padding: 20px; margin-bottom: 24px;
+}
+.ifnex-summary-row {
+ display: flex; justify-content: space-between; padding: 8px 0;
+ border-bottom: 1px dashed var(--ifnex-border);
+}
+.ifnex-summary-row:last-child { border-bottom: none; }
+.ifnex-summary-row.total {
+ font-size: 18px; font-weight: 700; color: var(--ifnex-primary-dark);
+ padding-top: 12px; margin-top: 8px; border-top: 2px solid var(--ifnex-primary);
+}
+
+.ifnex-payment-methods { display: grid; gap: 16px; }
+.ifnex-payment-option {
+ border: 2px solid var(--ifnex-border); border-radius: 8px;
+ padding: 20px; transition: all 0.2s;
+}
+.ifnex-payment-option:hover { border-color: var(--ifnex-primary); }
+.ifnex-payment-option.disabled { opacity: 0.5; pointer-events: none; }
+.ifnex-payment-option h3 { margin-top: 0; color: var(--ifnex-dark); }
+.ifnex-payment-option p { color: var(--ifnex-gray); margin: 8px 0 16px; }
+
+.ifnex-warning {
+ background: #fef3c7; color: #92400e; padding: 10px;
+ border-radius: 6px; font-size: 13px; margin-bottom: 12px;
+}
+
+.ifnex-payment-footer {
+ margin-top: 24px; padding-top: 20px;
+ border-top: 1px solid var(--ifnex-border); text-align: center;
+}
+
+/* ═══ Order Detail Page ═══ */
+.ifnex-order-detail { max-width: 900px; margin: 0 auto; padding: 20px 0; }
+
+.ifnex-detail-header {
+ display: flex; justify-content: space-between; align-items: center;
+ margin-bottom: 20px; flex-wrap: wrap; gap: 12px;
+}
+.ifnex-detail-title { display: flex; align-items: center; gap: 12px; }
+.ifnex-detail-title h2 { margin: 0; color: var(--ifnex-dark); }
+.ifnex-awb-code {
+ background: var(--ifnex-dark); color: #fff; padding: 6px 12px;
+ border-radius: 6px; font-size: 14px; font-weight: 700;
+}
+.ifnex-badge-lg { padding: 8px 20px; font-size: 14px; }
+
+.ifnex-detail-actions {
+ display: flex; gap: 8px; flex-wrap: wrap; margin-bottom: 24px;
+ padding: 16px; background: var(--ifnex-light); border-radius: 8px;
+}
+
+.ifnex-detail-section {
+ background: #fff; border: 1px solid var(--ifnex-border);
+ border-radius: var(--ifnex-radius); padding: 24px; margin-bottom: 20px;
+}
+.ifnex-detail-section h3 {
+ margin-top: 0; color: var(--ifnex-dark); padding-bottom: 12px;
+ border-bottom: 1px solid var(--ifnex-border); margin-bottom: 16px;
+}
+
+.ifnex-route-detail {
+ display: flex; align-items: center; gap: 20px; padding: 20px;
+}
+.ifnex-route-point {
+ flex: 1; text-align: center; padding: 20px;
+ background: var(--ifnex-light); border-radius: 8px;
+}
+.ifnex-point-label {
+ font-size: 12px; color: var(--ifnex-gray); text-transform: uppercase;
+ letter-spacing: 1px; margin-bottom: 8px;
+}
+.ifnex-point-country {
+ font-size: 20px; font-weight: 700; color: var(--ifnex-dark);
+ margin-bottom: 4px;
+}
+.ifnex-point-iso {
+ font-size: 13px; color: var(--ifnex-gray); font-weight: 600;
+}
+.ifnex-route-line {
+ display: flex; align-items: center; justify-content: center;
+ flex: 0.5;
+}
+.ifnex-plane-icon { font-size: 32px; }
+
+.ifnex-info-grid {
+ display: grid; grid-template-columns: repeat(2, 1fr); gap: 12px;
+}
+.ifnex-info-item.full { grid-column: 1 / -1; }
+.ifnex-info-label {
+ display: block; font-size: 12px; color: var(--ifnex-gray);
+ text-transform: uppercase; letter-spacing: 0.5px; margin-bottom: 4px;
+}
+.ifnex-info-value { font-size: 15px; color: var(--ifnex-dark); font-weight: 500; }
+
+.ifnex-price-breakdown { padding: 8px 0; }
+.ifnex-price-row {
+ display: flex; justify-content: space-between; padding: 10px 0;
+ border-bottom: 1px dashed var(--ifnex-border); font-size: 14px;
+}
+.ifnex-price-row:last-child { border-bottom: none; }
+.ifnex-price-row.total {
+ font-size: 18px; font-weight: 800; color: var(--ifnex-primary-dark);
+ padding-top: 16px; margin-top: 8px; border-top: 2px solid var(--ifnex-primary);
+ border-bottom: none;
+}
+
+.ifnex-text-success { color: var(--ifnex-success); font-weight: 600; }
+.ifnex-text-warning { color: var(--ifnex-warning); font-weight: 600; }
+
+/* Timeline */
+.ifnex-timeline { position: relative; padding-right: 20px; }
+.ifnex-timeline::before {
+ content: ''; position: absolute; right: 8px; top: 0; bottom: 0;
+ width: 2px; background: var(--ifnex-border);
+}
+.ifnex-timeline-item {
+ position: relative; padding-right: 32px; padding-bottom: 20px;
+}
+.ifnex-timeline-item:last-child { padding-bottom: 0; }
+.ifnex-timeline-dot {
+ position: absolute; right: -20px; top: 4px;
+ width: 16px; height: 16px; border-radius: 50%;
+ background: var(--ifnex-primary); border: 3px solid #fff;
+ box-shadow: 0 0 0 2px var(--ifnex-border);
+}
+.ifnex-timeline-content {
+ background: var(--ifnex-light); padding: 12px 16px;
+ border-radius: 8px; border-right: 3px solid var(--ifnex-primary);
+}
+.ifnex-timeline-header {
+ display: flex; justify-content: space-between; flex-wrap: wrap;
+ gap: 8px; margin-bottom: 4px;
+}
+.ifnex-timeline-date { font-size: 12px; color: var(--ifnex-gray); }
+.ifnex-timeline-location { font-size: 13px; color: var(--ifnex-gray); }
+.ifnex-empty-timeline { color: var(--ifnex-gray); font-style: italic; }
+
+@media (max-width: 640px) {
+ .ifnex-route-detail { flex-direction: column; }
+ .ifnex-route-line { transform: rotate(90deg); }
+ .ifnex-info-grid { grid-template-columns: 1fr; }
+}
+
+@media print {
+ .ifnex-detail-actions { display: none !important; }
+ .ifnex-order-detail { max-width: 100%; }
}
\ No newline at end of file
diff --git a/03_WordPress/wp-content/plugins/ifnex-bridge/ifnex-bridge.php b/03_WordPress/wp-content/plugins/ifnex-bridge/ifnex-bridge.php
index b6df2c9..2fb5f32 100644
--- a/03_WordPress/wp-content/plugins/ifnex-bridge/ifnex-bridge.php
+++ b/03_WordPress/wp-content/plugins/ifnex-bridge/ifnex-bridge.php
@@ -39,11 +39,13 @@ function ifnex_settings_page_html() {
if (isset($_POST['ifnex_api_url']) && check_admin_referer('ifnex_settings_save')) {
update_option('ifnex_api_url', sanitize_url($_POST['ifnex_api_url']));
update_option('ifnex_api_key', sanitize_text_field($_POST['ifnex_api_key']));
+ update_option('ifnex_bridge_api_key', sanitize_text_field($_POST['ifnex_bridge_api_key'] ?? ''));
echo '
';
}
$api_url = get_option('ifnex_api_url', 'http://localhost:8000/api/v1');
$api_key = get_option('ifnex_api_key', '');
+ $bridge_api_key = get_option('ifnex_bridge_api_key', '');
?>
تنظیمات IFNEX Logistics Bridge
@@ -52,15 +54,36 @@ function ifnex_settings_page_html() {
+
+
+
راهنمای راهاندازی
+
+ - در فایل
.env لاراول، خط IFNEX_BRIDGE_API_KEY=your-secret-key را تنظیم کنید
+ - همان مقدار را در فیلد "Bridge API Key" بالا وارد کنید
+ - کاربران وردپرس باید در لاراول با همان ایمیل موجود باشند (از
php artisan ifnex:sync-wp-users استفاده کنید)
+
-
👁️ جزئیات
@@ -406,8 +406,8 @@ function ifnex_orders_list_shortcode($atts) {
-
+
💳 پرداخت
@@ -604,4 +604,496 @@ function ifnex_order_form_shortcode($atts) {
برای پرداخت، باید وارد شوید.';
+ }
+
+ $atts = shortcode_atts(array('order_id' => ''), $atts);
+
+ // دریافت order_id از URL parameter
+ $order_id = $atts['order_id'] ?: ($_GET['order_id'] ?? 0);
+
+ if (!$order_id) {
+ return 'شناسه سفارش مشخص نشده است.
';
+ }
+
+ $bridge = new IFNEX_User_Bridge();
+ $user_id = get_current_user_id();
+
+ $order = $bridge->get_customer_order($user_id, $order_id);
+
+ if (is_wp_error($order)) {
+ return '' . esc_html($order->get_error_message()) . '
';
+ }
+
+ $data = $order['data'];
+
+ // بررسی وضعیت
+ if ($data['status']['value'] !== 'pending_payment') {
+ return '
+
✅ این سفارش قبلاً پرداخت شده است
+
وضعیت: ' . esc_html($data['status']['label']) . '
+
بازگشت به سفارشات
+
';
+ }
+
+ $balance = $bridge->get_wallet_balance($user_id);
+ $wallet_balance = is_wp_error($balance) ? 0 : ($balance['balance'] ?? 0);
+ $can_pay_wallet = $wallet_balance >= $data['total_fee'];
+
+ ob_start();
+ ?>
+
+
+
💳 پرداخت سفارش
+
+
+
+ شماره پیگیری:
+
+
+
+ مسیر:
+ →
+
+
+ مبلغ قابل پرداخت:
+ ریال
+
+
+
+
+
+
+
💰 پرداخت از کیف پول
+
موجودی فعلی: ریال
+
+
+
+
⚠️ موجودی کافی نیست. لطفاً ابتدا کیف پول خود را شارژ کنید.
+
شارژ کیف پول
+
+
+
+
+
+
🏦 پرداخت از درگاه بانکی
+
انتقال به درگاه پرداخت امن (زرینپال)
+
+
+
+
+
+
+
+
+
+
+
+ برای مشاهده جزئیات، باید وارد شوید.';
+ }
+
+ $atts = shortcode_atts(array('order_id' => ''), $atts);
+ $order_id = $atts['order_id'] ?: ($_GET['order_id'] ?? 0);
+
+ // اگر از URL pretty استفاده شده (order-detail/14/)
+ if (!$order_id && isset($_SERVER['REQUEST_URI'])) {
+ if (preg_match('/order-detail\/(\d+)/', $_SERVER['REQUEST_URI'], $matches)) {
+ $order_id = $matches[1];
+ }
+ }
+
+ if (!$order_id) {
+ return 'شناسه سفارش مشخص نشده است.
';
+ }
+
+ $bridge = new IFNEX_User_Bridge();
+ $user_id = get_current_user_id();
+
+ $order = $bridge->get_customer_order($user_id, $order_id);
+
+ if (is_wp_error($order)) {
+ return '' . esc_html($order->get_error_message()) . '
';
+ }
+
+ $data = $order['data'];
+ $status = $data['status'];
+
+ ob_start();
+ ?>
+
+
+
+
+
+
+
+
+
+
+
+
+
📦 اطلاعات مرسوله
+
+
+ نوع سرویس
+
+
+
+ جهت ارسال
+
+
+
+
+
+ وزن واقعی
+ kg
+
+
+ وزن قابل محاسبه
+ kg
+
+
+ تاریخ ثبت
+
+
+
+
+
+
+
+
💰 اطلاعات مالی
+
+
+ مبلغ خالص (درهم):
+ AED
+
+
+ مبلغ خالص (ریال):
+ ریال
+
+
+ مبلغ کل:
+ ریال
+
+
+ وضعیت پرداخت:
+
+
+
+
+
+
+
+
+
+
+
👤 اطلاعات فرستنده
+
+
+ نام
+
+
+
+ تلفن
+
+
+
+ آدرس
+
+
+
+
+
+
+
+
+
+
📮 اطلاعات گیرنده
+
+
+ نام
+
+
+
+ تلفن
+
+
+
+ آدرس
+
+
+
+
+
+
+
+
+
+
+
+
📍 تاریخچه رهگیری
+
هنوز رویدادی ثبت نشده است.
+
+
+
+
+
+ time()) {
return $token;
}
- $user = get_userdata($user_id);
- if (!$user) {
+ // دریافت اطلاعات کاربر وردپرس
+ $wp_user = get_userdata($user_id);
+ if (!$wp_user) {
return false;
}
- $token = $this->request_token($user->user_email, $this->generate_temp_password($user_id));
-
+ // درخواست توکن از Bridge endpoint (جدید)
+ $token = $this->request_bridge_token(
+ $user_id,
+ $wp_user->user_email,
+ $wp_user->display_name
+ );
+
if ($token) {
update_user_meta($user_id, 'ifnex_laravel_token', $token);
update_user_meta($user_id, 'ifnex_laravel_token_expiry', date('Y-m-d H:i:s', strtotime('+30 days')));
@@ -43,13 +50,18 @@ class IFNEX_User_Bridge {
return $token;
}
- /**
- * درخواست توکن از لاراول
+ /**
+ * درخواست توکن از Bridge endpoint جدید
+ * این روش نیازی به رمز عبور ندارد و از API Key مشترک استفاده میکند
*/
- private function request_token($email, $password) {
- //$url = $this->api_url . '/sanctum/token';
-
- $url = $this->api_url . '/auth/login';
+ private function request_bridge_token($wp_user_id, $email, $name) {
+ $url = $this->api_url . '/bridge/login';
+ $bridge_key = get_option('ifnex_bridge_api_key', '');
+
+ if (empty($bridge_key)) {
+ error_log('IFNEX: Bridge API Key is not configured in WordPress settings');
+ return false;
+ }
$args = array(
'headers' => array(
@@ -57,8 +69,10 @@ class IFNEX_User_Bridge {
'Accept' => 'application/json',
),
'body' => json_encode(array(
- 'email' => $email,
- 'password' => $password,
+ 'bridge_api_key' => $bridge_key,
+ 'wp_user_id' => $wp_user_id,
+ 'wp_user_email' => $email,
+ 'wp_user_name' => $name,
'token_name' => 'wordpress-bridge-' . get_current_blog_id(),
)),
'timeout' => 30,
@@ -67,6 +81,7 @@ class IFNEX_User_Bridge {
$response = wp_remote_post($url, $args);
if (is_wp_error($response)) {
+ error_log('IFNEX Bridge Error: ' . $response->get_error_message());
return false;
}
@@ -77,35 +92,23 @@ class IFNEX_User_Bridge {
return $data['token'];
}
+ error_log('IFNEX Bridge Token Error: ' . ($data['message'] ?? 'Unknown error'));
return false;
}
- /**
- * تولید رمز عبور موقت برای کاربر وردپرس
- */
- private function generate_temp_password($user_id) {
- $temp_pass = get_user_meta($user_id, 'ifnex_temp_password', true);
-
- if (!$temp_pass) {
- $temp_pass = wp_generate_password(32, true, true);
- update_user_meta($user_id, 'ifnex_temp_password', $temp_pass);
- }
-
- return $temp_pass;
- }
-
- /**
- * ارسال درخواست احراز هویت به API لاراول
+ /**
+ * ارسال درخواست احراز هویتشده به API لاراول
*/
public function authenticated_request($user_id, $endpoint, $method = 'GET', $data = array()) {
$token = $this->get_user_token($user_id);
-
+
if (!$token) {
return new WP_Error('no_token', 'توکن احراز هویت یافت نشد.');
}
$url = $this->api_url . $endpoint;
-
+ $method = strtoupper($method);
+
$args = array(
'headers' => array(
'Authorization' => 'Bearer ' . $token,
@@ -113,16 +116,16 @@ class IFNEX_User_Bridge {
'Content-Type' => 'application/json',
),
'timeout' => 30,
+ 'method' => $method, // ✅ همیشه متد درخواستی استفاده شود
);
- if ($method === 'POST' && !empty($data)) {
- $args['body'] = json_encode($data);
- $args['method'] = 'POST';
- } else {
- $args['method'] = 'GET';
- if (!empty($data)) {
- $url = add_query_arg($data, $url);
- }
+ // ✅ اصلاح: بر اساس متد، data را اضافه کن
+ if (in_array($method, ['POST', 'PUT', 'PATCH'])) {
+ // حتی اگر data خالی باشد، body را خالی بفرست
+ $args['body'] = json_encode($data ?: new \stdClass());
+ } elseif ($method === 'GET' && !empty($data)) {
+ // برای GET، data را به URL اضافه کن
+ $url = add_query_arg($data, $url);
}
$response = wp_remote_request($url, $args);
@@ -132,13 +135,22 @@ class IFNEX_User_Bridge {
}
$body = wp_remote_retrieve_body($response);
+ $code = wp_remote_retrieve_response_code($response);
$data = json_decode($body, true);
- if ($response['response']['code'] === 401) {
+ // لاگ برای دیباگ
+ error_log("IFNEX API [{$method}] {$endpoint} => Status: {$code}");
+
+ if ($code === 401) {
$this->invalidate_token(get_current_user_id());
return new WP_Error('token_expired', 'توکن منقضی شده است. لطفاً دوباره وارد شوید.');
}
+ // اگر پاسخ JSON نیست
+ if (!is_array($data)) {
+ return new WP_Error('invalid_response', 'پاسخ نامعتبر از سرور: ' . substr($body, 0, 200));
+ }
+
return $data;
}
@@ -427,10 +439,12 @@ function ifnex_enqueue_order_assets() {
global $post;
if (!is_a($post, 'WP_Post')) return;
- $has_order_shortcodes =
- has_shortcode($post->post_content, 'ifnex_orders_list') ||
- has_shortcode($post->post_content, 'ifnex_order_form') ||
- has_shortcode($post->post_content, 'ifnex_user_profile');
+ $has_order_shortcodes =
+ has_shortcode($post->post_content, 'ifnex_orders_list') ||
+ has_shortcode($post->post_content, 'ifnex_order_form') ||
+ has_shortcode($post->post_content, 'ifnex_user_profile') ||
+ has_shortcode($post->post_content, 'ifnex_order_payment') ||
+ has_shortcode($post->post_content, 'ifnex_order_detail');
if ($has_order_shortcodes) {
// ✅ اصلاح: dirname(__FILE__) یک سطح بالاتر میرود
@@ -461,4 +475,76 @@ function ifnex_enqueue_order_assets() {
'orders_url' => home_url('/my-orders/'),
));
}
+}
+
+// ══════════════════════════════════════════════════════════════
+// AJAX Handlers برای پرداخت سفارش
+// ══════════════════════════════════════════════════════════════
+
+add_action('wp_ajax_ifnex_pay_order_wallet', 'ifnex_pay_order_wallet_ajax');
+function ifnex_pay_order_wallet_ajax() {
+ check_ajax_referer('ifnex_ajax_nonce', 'nonce');
+ if (!is_user_logged_in()) wp_send_json_error('باید وارد شوید.');
+
+ $order_id = intval($_POST['order_id'] ?? 0);
+ if (!$order_id) wp_send_json_error('شناسه سفارش نامعتبر است.');
+
+ $bridge = new IFNEX_User_Bridge();
+ $result = $bridge->authenticated_request(
+ get_current_user_id(),
+ "/customer/orders/{$order_id}/pay-wallet",
+ 'POST'
+ );
+
+ if (is_wp_error($result)) wp_send_json_error($result->get_error_message());
+ if (isset($result['success']) && !$result['success']) {
+ wp_send_json_error($result['message'] ?? 'خطا در پرداخت');
+ }
+
+ wp_send_json_success($result);
+}
+
+add_action('wp_ajax_ifnex_pay_order_gateway', 'ifnex_pay_order_gateway_ajax');
+function ifnex_pay_order_gateway_ajax() {
+ check_ajax_referer('ifnex_ajax_nonce', 'nonce');
+ if (!is_user_logged_in()) wp_send_json_error('باید وارد شوید.');
+
+ $order_id = intval($_POST['order_id'] ?? 0);
+ $callback_url = sanitize_url($_POST['callback_url'] ?? '');
+
+ if (!$order_id) wp_send_json_error('شناسه سفارش نامعتبر است.');
+ if (!$callback_url) wp_send_json_error('آدرس بازگشت نامعتبر است.');
+
+ $bridge = new IFNEX_User_Bridge();
+ $result = $bridge->authenticated_request(
+ get_current_user_id(),
+ "/customer/orders/{$order_id}/pay-gateway",
+ 'POST',
+ ['frontend_callback' => $callback_url]
+ );
+
+ if (is_wp_error($result)) wp_send_json_error($result->get_error_message());
+ if (isset($result['success']) && !$result['success']) {
+ wp_send_json_error($result['message'] ?? 'خطا در اتصال به درگاه');
+ }
+
+ wp_send_json_success($result);
+}
+
+// Enqueue برای صفحه پرداخت
+add_action('wp_enqueue_scripts', 'ifnex_enqueue_payment_assets');
+function ifnex_enqueue_payment_assets() {
+ global $post;
+ if (!is_a($post, 'WP_Post')) return;
+
+ if (has_shortcode($post->post_content, 'ifnex_order_payment')) {
+ $plugin_url = plugin_dir_url(dirname(__FILE__));
+ wp_enqueue_style('ifnex-orders-css', $plugin_url . 'assets/css/ifnex-orders.css', array(), '1.3.0');
+ wp_enqueue_script('jquery');
+ wp_localize_script('jquery', 'ifnex_ajax', array(
+ 'ajax_url' => admin_url('admin-ajax.php'),
+ 'nonce' => wp_create_nonce('ifnex_ajax_nonce'),
+ 'orders_url' => home_url('/my-orders/'),
+ ));
+ }
}
\ No newline at end of file