feat: Complete WordPress customer portal

This commit is contained in:
Kazem Alghasi 2026-08-10 13:08:53 +03:30
parent aa8fb17230
commit bac2a7bb25
4 changed files with 811 additions and 51 deletions

View File

@ -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%; }
}

View File

@ -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 '<div class="notice notice-success"><p>تنظیمات ذخیره شد.</p></div>';
}
$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', '');
?>
<div class="wrap">
<h1>تنظیمات IFNEX Logistics Bridge</h1>
@ -52,15 +54,36 @@ function ifnex_settings_page_html() {
<table class="form-table">
<tr>
<th scope="row"><label for="ifnex_api_url">API URL</label></th>
<td><input type="text" id="ifnex_api_url" name="ifnex_api_url" value="<?php echo esc_attr($api_url); ?>" class="regular-text" placeholder="http://localhost:8000/api/v1"></td>
<td>
<input type="text" id="ifnex_api_url" name="ifnex_api_url" value="<?php echo esc_attr($api_url); ?>" class="regular-text" placeholder="http://localhost:8000/api/v1">
<p class="description">آدرس پایه API لاراول</p>
</td>
</tr>
<tr>
<th scope="row"><label for="ifnex_api_key">API Key</label></th>
<td><input type="text" id="ifnex_api_key" name="ifnex_api_key" value="<?php echo esc_attr($api_key); ?>" class="regular-text"></td>
<th scope="row"><label for="ifnex_api_key">API Key (عمومی)</label></th>
<td>
<input type="text" id="ifnex_api_key" name="ifnex_api_key" value="<?php echo esc_attr($api_key); ?>" class="regular-text">
<p class="description">برای APIهای عمومی مثل Tracking</p>
</td>
</tr>
<tr style="background: #fff3cd;">
<th scope="row"><label for="ifnex_bridge_api_key">🔐 Bridge API Key</label></th>
<td>
<input type="text" id="ifnex_bridge_api_key" name="ifnex_bridge_api_key" value="<?php echo esc_attr($bridge_api_key); ?>" class="regular-text">
<p class="description"><strong>مهم:</strong> باید با مقدار <code>IFNEX_BRIDGE_API_KEY</code> در فایل <code>.env</code> لاراول یکسان باشد.</p>
</td>
</tr>
</table>
<?php submit_button('ذخیره تنظیمات'); ?>
</form>
<hr>
<h2>راهنمای راه‌اندازی</h2>
<ol>
<li>در فایل <code>.env</code> لاراول، خط <code>IFNEX_BRIDGE_API_KEY=your-secret-key</code> را تنظیم کنید</li>
<li>همان مقدار را در فیلد "Bridge API Key" بالا وارد کنید</li>
<li>کاربران وردپرس باید در لاراول با همان ایمیل موجود باشند (از <code>php artisan ifnex:sync-wp-users</code> استفاده کنید)</li>
</ol>
</div>
<?php
}
}

View File

@ -393,7 +393,7 @@ function ifnex_orders_list_shortcode($atts) {
</div>
<div class="ifnex-order-actions">
<a href="<?php echo esc_url(home_url('/order-detail/' . $order['id'] . '/')); ?>"
<a href="<?php echo esc_url(home_url('/order-detail/?order_id=' . $order['id'])); ?>"
class="ifnex-btn ifnex-btn-sm ifnex-btn-info">
👁️ جزئیات
</a>
@ -406,8 +406,8 @@ function ifnex_orders_list_shortcode($atts) {
<?php endif; ?>
<?php if (!$order['status']['is_paid']): ?>
<a href="<?php echo esc_url(home_url('/payment/' . $order['id'] . '/')); ?>"
class="ifnex-btn ifnex-btn-sm ifnex-btn-success">
<a href="<?php echo esc_url(home_url('/order-payment/?order_id=' . $order['id'])); ?>"
class="ifnex-btn ifnex-btn-sm ifnex-btn-success">
💳 پرداخت
</a>
<?php endif; ?>
@ -604,4 +604,496 @@ function ifnex_order_form_shortcode($atts) {
</div>
<?php
return ob_get_clean();
}
// ══════════════════════════════════════════════════════════════
// شورت‌کد صفحه پرداخت سفارش
// ══════════════════════════════════════════════════════════════
add_shortcode('ifnex_order_payment', 'ifnex_order_payment_shortcode');
function ifnex_order_payment_shortcode($atts) {
if (!is_user_logged_in()) {
return '<p class="ifnex-error">برای پرداخت، باید وارد شوید.</p>';
}
$atts = shortcode_atts(array('order_id' => ''), $atts);
// دریافت order_id از URL parameter
$order_id = $atts['order_id'] ?: ($_GET['order_id'] ?? 0);
if (!$order_id) {
return '<p class="ifnex-error">شناسه سفارش مشخص نشده است.</p>';
}
$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 '<p class="ifnex-error">' . esc_html($order->get_error_message()) . '</p>';
}
$data = $order['data'];
// بررسی وضعیت
if ($data['status']['value'] !== 'pending_payment') {
return '<div class="ifnex-success-box">
<h3> این سفارش قبلاً پرداخت شده است</h3>
<p>وضعیت: ' . esc_html($data['status']['label']) . '</p>
<a href="' . esc_url(home_url('/my-orders/')) . '" class="ifnex-btn ifnex-btn-primary">بازگشت به سفارشات</a>
</div>';
}
$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();
?>
<div class="ifnex-payment-page">
<div class="ifnex-payment-card">
<h2>💳 پرداخت سفارش</h2>
<div class="ifnex-order-summary">
<div class="ifnex-summary-row">
<span>شماره پیگیری:</span>
<strong><?php echo esc_html($data['awb_no']); ?></strong>
</div>
<div class="ifnex-summary-row">
<span>مسیر:</span>
<strong><?php echo esc_html($data['from_country']['name']); ?> → <?php echo esc_html($data['to_country']['name']); ?></strong>
</div>
<div class="ifnex-summary-row total">
<span>مبلغ قابل پرداخت:</span>
<strong><?php echo number_format($data['total_fee']); ?> ریال</strong>
</div>
</div>
<div class="ifnex-payment-methods">
<!-- پرداخت از کیف پول -->
<div class="ifnex-payment-option <?php echo $can_pay_wallet ? '' : 'disabled'; ?>">
<h3>💰 پرداخت از کیف پول</h3>
<p>موجودی فعلی: <strong><?php echo number_format($wallet_balance); ?> ریال</strong></p>
<?php if ($can_pay_wallet): ?>
<button class="ifnex-btn ifnex-btn-success ifnex-pay-wallet" data-order-id="<?php echo esc_attr($order_id); ?>">
پرداخت فوری از کیف پول
</button>
<?php else: ?>
<p class="ifnex-warning">⚠️ موجودی کافی نیست. لطفاً ابتدا کیف پول خود را شارژ کنید.</p>
<a href="<?php echo esc_url(home_url('/wallet/')); ?>" class="ifnex-btn ifnex-btn-info">شارژ کیف پول</a>
<?php endif; ?>
</div>
<!-- پرداخت از درگاه -->
<div class="ifnex-payment-option">
<h3>🏦 پرداخت از درگاه بانکی</h3>
<p>انتقال به درگاه پرداخت امن (زرین‌پال)</p>
<button class="ifnex-btn ifnex-btn-primary ifnex-pay-gateway" data-order-id="<?php echo esc_attr($order_id); ?>">
انتقال به درگاه بانکی
</button>
</div>
</div>
<div id="ifnex-payment-message" style="display:none; margin-top:20px;"></div>
<div class="ifnex-payment-footer">
<a href="<?php echo esc_url(home_url('/my-orders/')); ?>" class="ifnex-btn ifnex-btn-sm">
بازگشت به سفارشات
</a>
</div>
</div>
</div>
<script>
jQuery(document).ready(function($) {
// پرداخت از کیف پول
$('.ifnex-pay-wallet').on('click', function() {
if (!confirm('آیا از پرداخت از کیف پول اطمینان دارید؟')) return;
var btn = $(this);
var orderId = btn.data('order-id');
console.log('═══════════════════════════════════════');
console.log('🚀 Starting wallet payment');
console.log('Order ID:', orderId);
console.log('AJAX URL:', ifnex_ajax.ajax_url);
console.log('Nonce:', ifnex_ajax.nonce);
console.log('═══════════════════════════════════════');
btn.prop('disabled', true).text('در حال پرداخت...');
$.ajax({
url: ifnex_ajax.ajax_url,
method: 'POST',
data: {
action: 'ifnex_pay_order_wallet',
order_id: orderId,
nonce: ifnex_ajax.nonce
},
beforeSend: function() {
console.log('📤 Sending AJAX request...');
},
success: function(res) {
console.log('📥 Response received:', res);
console.log('Success:', res.success);
console.log('Data:', res.data);
if (res.success) {
console.log('✅ Payment successful!');
console.log('New status:', res.data?.status?.value);
console.log('Transaction ID:', res.data?.transaction_id);
$('#ifnex-payment-message')
.removeClass('ifnex-error-box')
.addClass('ifnex-success-box')
.html('✅ پرداخت با موفقیت انجام شد! در حال انتقال...')
.show();
setTimeout(function() {
window.location.href = ifnex_ajax.orders_url + '?t=' + Date.now();
}, 2000);
} else {
console.error('❌ Payment failed:', res.data);
btn.prop('disabled', false).text('پرداخت فوری از کیف پول');
$('#ifnex-payment-message')
.addClass('ifnex-error-box')
.text('خطا: ' + (res.data || 'نامشخص'))
.show();
}
},
error: function(xhr, status, error) {
console.error('═══════════════════════════════════════');
console.error('❌ AJAX ERROR');
console.error('Status:', status);
console.error('Error:', error);
console.error('Response Code:', xhr.status);
console.error('Response Text:', xhr.responseText);
console.error('═══════════════════════════════════════');
btn.prop('disabled', false).text('پرداخت فوری از کیف پول');
$('#ifnex-payment-message')
.addClass('ifnex-error-box')
.html('خطا در ارتباط با سرور<br><small>' + error + '</small>')
.show();
}
});
});
// پرداخت از درگاه
$('.ifnex-pay-gateway').on('click', function() {
var btn = $(this);
var orderId = btn.data('order-id');
btn.prop('disabled', true).text('در حال اتصال به درگاه...');
$.ajax({
url: ifnex_ajax.ajax_url,
method: 'POST',
data: {
action: 'ifnex_pay_order_gateway',
order_id: orderId,
callback_url: window.location.href,
nonce: ifnex_ajax.nonce
},
success: function(res) {
if (res.success && res.data.payment_url) {
window.location.href = res.data.payment_url;
} else {
btn.prop('disabled', false).text('انتقال به درگاه بانکی');
$('#ifnex-payment-message')
.addClass('ifnex-error-box')
.text('خطا: ' + (res.data || 'خطا در اتصال به درگاه'))
.show();
}
},
error: function() {
btn.prop('disabled', false).text('انتقال به درگاه بانکی');
$('#ifnex-payment-message')
.addClass('ifnex-error-box')
.text('خطا در ارتباط با سرور')
.show();
}
});
});
});
</script>
<?php
return ob_get_clean();
}
// ══════════════════════════════════════════════════════════════
// شورت‌کد جزئیات سفارش
// ══════════════════════════════════════════════════════════════
add_shortcode('ifnex_order_detail', 'ifnex_order_detail_shortcode');
function ifnex_order_detail_shortcode($atts) {
if (!is_user_logged_in()) {
return '<p class="ifnex-error">برای مشاهده جزئیات، باید وارد شوید.</p>';
}
$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 '<p class="ifnex-error">شناسه سفارش مشخص نشده است.</p>';
}
$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 '<p class="ifnex-error">' . esc_html($order->get_error_message()) . '</p>';
}
$data = $order['data'];
$status = $data['status'];
ob_start();
?>
<div class="ifnex-order-detail">
<!-- هدر با وضعیت -->
<div class="ifnex-detail-header">
<div class="ifnex-detail-title">
<h2>📦 جزئیات سفارش</h2>
<code class="ifnex-awb-code"><?php echo esc_html($data['awb_no']); ?></code>
</div>
<span class="ifnex-badge ifnex-badge-<?php echo esc_attr($status['color']); ?> ifnex-badge-lg">
<?php echo esc_html($status['label']); ?>
</span>
</div>
<!-- دکمه‌های عملیاتی -->
<div class="ifnex-detail-actions">
<a href="<?php echo esc_url(home_url('/my-orders/')); ?>" class="ifnex-btn ifnex-btn-sm">
بازگشت به سفارشات
</a>
<?php if (!$status['is_paid']): ?>
<a href="<?php echo esc_url(home_url('/order-payment/?order_id=' . $order_id)); ?>"
class="ifnex-btn ifnex-btn-sm ifnex-btn-success">
💳 پرداخت
</a>
<?php endif; ?>
<?php if ($status['can_cancel']): ?>
<button class="ifnex-btn ifnex-btn-sm ifnex-btn-danger ifnex-cancel-order-detail"
data-order-id="<?php echo esc_attr($order_id); ?>">
لغو سفارش
</button>
<?php endif; ?>
<button onclick="window.print()" class="ifnex-btn ifnex-btn-sm ifnex-btn-info">
🖨️ چاپ
</button>
</div>
<!-- مسیر ارسال -->
<div class="ifnex-detail-section">
<h3>🗺️ مسیر ارسال</h3>
<div class="ifnex-route-detail">
<div class="ifnex-route-point from">
<div class="ifnex-point-label">از</div>
<div class="ifnex-point-country">
<?php echo esc_html($data['from_country']['name'] ?? '—'); ?>
</div>
<div class="ifnex-point-iso">
<?php echo esc_html($data['from_country']['iso_code'] ?? ''); ?>
</div>
</div>
<div class="ifnex-route-line">
<div class="ifnex-plane-icon">✈️</div>
</div>
<div class="ifnex-route-point to">
<div class="ifnex-point-label">به</div>
<div class="ifnex-point-country">
<?php echo esc_html($data['to_country']['name'] ?? '—'); ?>
</div>
<div class="ifnex-point-iso">
<?php echo esc_html($data['to_country']['iso_code'] ?? ''); ?>
</div>
</div>
</div>
</div>
<!-- اطلاعات مرسوله -->
<div class="ifnex-detail-section">
<h3>📦 اطلاعات مرسوله</h3>
<div class="ifnex-info-grid">
<div class="ifnex-info-item">
<span class="ifnex-info-label">نوع سرویس</span>
<span class="ifnex-info-value"><?php echo esc_html($data['type'] ?? '—'); ?></span>
</div>
<div class="ifnex-info-item">
<span class="ifnex-info-label">جهت ارسال</span>
<span class="ifnex-info-value">
<?php echo ($data['direction'] ?? '') === 'export' ? 'صادرات' : 'واردات'; ?>
</span>
</div>
<div class="ifnex-info-item">
<span class="ifnex-info-label">وزن واقعی</span>
<span class="ifnex-info-value"><?php echo esc_html($data['weight'] ?? '—'); ?> kg</span>
</div>
<div class="ifnex-info-item">
<span class="ifnex-info-label">وزن قابل محاسبه</span>
<span class="ifnex-info-value"><?php echo esc_html($data['chargeable_weight'] ?? '—'); ?> kg</span>
</div>
<div class="ifnex-info-item">
<span class="ifnex-info-label">تاریخ ثبت</span>
<span class="ifnex-info-value"><?php echo esc_html($data['created_at_jalali'] ?? '—'); ?></span>
</div>
</div>
</div>
<!-- اطلاعات مالی -->
<div class="ifnex-detail-section">
<h3>💰 اطلاعات مالی</h3>
<div class="ifnex-price-breakdown">
<div class="ifnex-price-row">
<span>مبلغ خالص (درهم):</span>
<span><?php echo number_format($data['net_dirham'] ?? 0); ?> AED</span>
</div>
<div class="ifnex-price-row">
<span>مبلغ خالص (ریال):</span>
<span><?php echo number_format($data['net_rial'] ?? 0); ?> ریال</span>
</div>
<div class="ifnex-price-row total">
<span>مبلغ کل:</span>
<span><?php echo number_format($data['total_fee'] ?? 0); ?> ریال</span>
</div>
<div class="ifnex-price-row">
<span>وضعیت پرداخت:</span>
<span class="<?php echo $status['is_paid'] ? 'ifnex-text-success' : 'ifnex-text-warning'; ?>">
<?php echo $status['is_paid'] ? '✅ پرداخت شده' : '⏳ در انتظار پرداخت'; ?>
</span>
</div>
</div>
</div>
<!-- اطلاعات فرستنده -->
<?php if (!empty($data['sender_name']) || !empty($data['sender_phone'])): ?>
<div class="ifnex-detail-section">
<h3>👤 اطلاعات فرستنده</h3>
<div class="ifnex-info-grid">
<div class="ifnex-info-item">
<span class="ifnex-info-label">نام</span>
<span class="ifnex-info-value"><?php echo esc_html($data['sender_name'] ?? '—'); ?></span>
</div>
<div class="ifnex-info-item">
<span class="ifnex-info-label">تلفن</span>
<span class="ifnex-info-value"><?php echo esc_html($data['sender_phone'] ?? '—'); ?></span>
</div>
<div class="ifnex-info-item full">
<span class="ifnex-info-label">آدرس</span>
<span class="ifnex-info-value"><?php echo esc_html($data['sender_address'] ?? '—'); ?></span>
</div>
</div>
</div>
<?php endif; ?>
<!-- اطلاعات گیرنده -->
<?php if (!empty($data['receiver_name']) || !empty($data['receiver_phone'])): ?>
<div class="ifnex-detail-section">
<h3>📮 اطلاعات گیرنده</h3>
<div class="ifnex-info-grid">
<div class="ifnex-info-item">
<span class="ifnex-info-label">نام</span>
<span class="ifnex-info-value"><?php echo esc_html($data['receiver_name'] ?? '—'); ?></span>
</div>
<div class="ifnex-info-item">
<span class="ifnex-info-label">تلفن</span>
<span class="ifnex-info-value"><?php echo esc_html($data['receiver_phone'] ?? '—'); ?></span>
</div>
<div class="ifnex-info-item full">
<span class="ifnex-info-label">آدرس</span>
<span class="ifnex-info-value"><?php echo esc_html($data['receiver_address'] ?? '—'); ?></span>
</div>
</div>
</div>
<?php endif; ?>
<!-- Timeline رهگیری -->
<?php if (!empty($data['tracking_events'])): ?>
<div class="ifnex-detail-section">
<h3>📍 تاریخچه رهگیری</h3>
<div class="ifnex-timeline">
<?php foreach ($data['tracking_events'] as $event): ?>
<div class="ifnex-timeline-item">
<div class="ifnex-timeline-dot"></div>
<div class="ifnex-timeline-content">
<div class="ifnex-timeline-header">
<strong><?php echo esc_html($event['event_description'] ?? ''); ?></strong>
<span class="ifnex-timeline-date">
<?php echo esc_html($event['event_date'] ?? ''); ?>
<?php if (!empty($event['event_time'])): ?>
- <?php echo esc_html($event['event_time']); ?>
<?php endif; ?>
</span>
</div>
<?php if (!empty($event['location'])): ?>
<div class="ifnex-timeline-location">
📍 <?php echo esc_html($event['location']); ?>
</div>
<?php endif; ?>
</div>
</div>
<?php endforeach; ?>
</div>
</div>
<?php else: ?>
<div class="ifnex-detail-section">
<h3>📍 تاریخچه رهگیری</h3>
<p class="ifnex-empty-timeline">هنوز رویدادی ثبت نشده است.</p>
</div>
<?php endif; ?>
</div>
<script>
jQuery(document).ready(function($) {
$('.ifnex-cancel-order-detail').on('click', function() {
if (!confirm('آیا از لغو این سفارش اطمینان دارید؟')) return;
var btn = $(this);
var orderId = btn.data('order-id');
btn.prop('disabled', true).text('در حال لغو...');
$.ajax({
url: ifnex_ajax.ajax_url,
method: 'POST',
data: {
action: 'ifnex_cancel_order',
order_id: orderId,
nonce: ifnex_ajax.nonce
},
success: function(response) {
if (response.success) {
alert('سفارش با موفقیت لغو شد.');
location.reload();
} else {
alert('خطا: ' + response.data);
btn.prop('disabled', false).text('❌ لغو سفارش');
}
},
error: function() {
alert('خطا در ارتباط با سرور.');
btn.prop('disabled', false).text('❌ لغو سفارش');
}
});
});
});
</script>
<?php
return ob_get_clean();
}

View File

@ -18,23 +18,30 @@ class IFNEX_User_Bridge {
}
/**
* دریافت توکن Sanctum برای کاربر وردپرس
* دریافت توکن Sanctum از لاراول برای کاربر وردپرس
*/
public function get_user_token($user_id) {
$token = get_user_meta($user_id, 'ifnex_laravel_token', true);
$token_expiry = get_user_meta($user_id, 'ifnex_laravel_token_expiry', true);
// اگر توکن معتبر وجود دارد، از آن استفاده کن
if ($token && $token_expiry && strtotime($token_expiry) > 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/'),
));
}
}