Introduce a comprehensive address management system allowing users to
save, manage, and reuse sender and receiver addresses during the
ordering process. This feature bridges the WordPress frontend with the
Laravel backend API.
Key changes:
- Backend (Laravel):
- Added `AddressBookEntry` model and migration for storing addresses
with role (sender/receiver), usage tracking, and default status.
- Implemented `AddressBookController` with full CRUD functionality
secured via Sanctum and ownership validation.
- Integrated address usage tracking and upsert logic into the
`CustomerOrderController` to prevent duplicate entries.
- Added 18 new feature tests covering ownership, limits, and
validation.
- Frontend (WordPress):
- Added a new "Address Book" tab in the user dashboard with card-based
UI, filtering, and SweetAlert2 integration.
- Enhanced the order form with dropdown selectors for saved addresses
and "save this address" checkboxes for both sender and receiver.
- Implemented secure AJAX proxies in `user-bridge.php` with nonce
and sanitization.
- Documentation:
- Updated `AGENT.md` and `CLIENT_DELIVERY.md` to reflect phase 3.9
completion and updated the project roadmap.
- Removed obsolete `README-INSTALL.md`.
202 lines
12 KiB
PHP
202 lines
12 KiB
PHP
<?php
|
||
|
||
use App\Http\Controllers\Api\CustomerFinancialController;
|
||
use App\Http\Controllers\Api\AddressBookController;
|
||
use App\Http\Controllers\Api\CommitmentFormController;
|
||
use App\Http\Controllers\Api\MobileVerificationController;
|
||
use App\Http\Controllers\Api\Customer\CustomerOrderController;
|
||
use App\Http\Controllers\Api\DiscountCodeController;
|
||
use App\Http\Controllers\Api\MockGatewayController;
|
||
use App\Http\Controllers\Api\PaymentController;
|
||
use App\Http\Controllers\Api\PricingController;
|
||
use App\Http\Controllers\Api\StaffOrderController;
|
||
use App\Http\Controllers\Api\TrackController;
|
||
use App\Http\Controllers\Api\WalletController;
|
||
use App\Http\Middleware\ApiKeyMiddleware;
|
||
use App\Http\Middleware\StaffApiMiddleware;
|
||
use Illuminate\Support\Facades\Route;
|
||
use App\Http\Controllers\Api\AuthController;
|
||
use App\Http\Controllers\Api\BridgeAuthController;
|
||
use App\Http\Controllers\ShipmentPdfController;
|
||
|
||
|
||
// ══════════════════════════════════════════════════════════════
|
||
// Bridge Auth API (فقط برای پلاگین وردپرس - با API Key محافظت میشود)
|
||
// ══════════════════════════════════════════════════════════════
|
||
Route::post('/v1/bridge/login', [BridgeAuthController::class, 'login'])
|
||
->middleware('throttle:auth'); // Login — ۵/min per IP
|
||
|
||
// ══════════════════════════════════════════════════════════════
|
||
// API عمومی با API Key
|
||
// ══════════════════════════════════════════════════════════════
|
||
Route::middleware([ApiKeyMiddleware::class, 'throttle:public'])->prefix('v1')->group(function () {
|
||
Route::get('/track/{awb_no}', [TrackController::class, 'show']);
|
||
});
|
||
|
||
// ══════════════════════════════════════════════════════════════
|
||
// API عمومی (بدون نیاز به احراز هویت)
|
||
// ══════════════════════════════════════════════════════════════
|
||
Route::middleware(['throttle:public'])->prefix('v1')->group(function () {
|
||
Route::get('/countries', [CustomerOrderController::class, 'countries']);
|
||
});
|
||
|
||
// ══════════════════════════════════════════════════════════════
|
||
// Auth API (عمومی - برای دریافت توکن)
|
||
// ══════════════════════════════════════════════════════════════
|
||
Route::post('/v1/auth/login', [AuthController::class, 'login'])
|
||
->middleware('throttle:auth');
|
||
|
||
Route::middleware(['auth:sanctum'])->post('/v1/auth/logout', [AuthController::class, 'logout']);
|
||
|
||
// ══════════════════════════════════════════════════════════════
|
||
// API برای کاربران لاگینشده (با Sanctum / Bearer Token)
|
||
// ══════════════════════════════════════════════════════════════
|
||
Route::middleware(['auth:sanctum', 'throttle:customer'])->prefix('v1')->group(function () {
|
||
|
||
// ─── Wallet API (کاربر عادی) ───
|
||
Route::get('/wallet/balance', [WalletController::class, 'balance']);
|
||
Route::get('/wallet/transactions', [WalletController::class, 'transactions']);
|
||
|
||
// ─── Payment API ───
|
||
Route::post('/payment/redirect', [PaymentController::class, 'redirectToGateway'])
|
||
->middleware('throttle:wallet');
|
||
Route::get('/payment/check/{transaction}', [PaymentController::class, 'checkStatus'])
|
||
->middleware('throttle:wallet');
|
||
|
||
// ─── Admin Wallet API ───
|
||
Route::post('/wallet/admin-adjust', [WalletController::class, 'adminAdjust'])
|
||
->middleware('throttle:wallet');
|
||
Route::post('/wallet/{wallet}/freeze', [WalletController::class, 'freeze'])
|
||
->middleware('throttle:wallet');
|
||
Route::post('/wallet/{wallet}/unfreeze', [WalletController::class, 'unfreeze'])
|
||
->middleware('throttle:wallet');
|
||
Route::get('/wallet/{wallet}/activity-log', [WalletController::class, 'activityLog']);
|
||
|
||
// ─── Discount Codes (نیازمند احراز هویت) ───
|
||
Route::get('/discount-codes', [DiscountCodeController::class, 'index']);
|
||
Route::post('/discount-codes/validate', [DiscountCodeController::class, 'validate']);
|
||
|
||
// ─── Address Book (دفترچه آدرس — فاز ۳.۹) ───
|
||
Route::get('/addresses', [AddressBookController::class, 'index']);
|
||
Route::post('/addresses', [AddressBookController::class, 'store']);
|
||
Route::put('/addresses/{address}', [AddressBookController::class, 'update']);
|
||
Route::patch('/addresses/{address}', [AddressBookController::class, 'update']);
|
||
Route::delete('/addresses/{address}', [AddressBookController::class, 'destroy']);
|
||
Route::post('/addresses/{address}/default', [AddressBookController::class, 'setDefault']);
|
||
|
||
// ─── Commitment Forms (نیازمند احراز هویت) ───
|
||
Route::get('/commitment-forms', [CommitmentFormController::class, 'index']);
|
||
Route::get('/commitment-forms/{direction}', [CommitmentFormController::class, 'byDirection']);
|
||
|
||
// ─── Customer Orders API (جدید) ───
|
||
Route::prefix('customer')->group(function () {
|
||
// پروفایل و آمار
|
||
Route::get('/profile', [CustomerOrderController::class, 'profile']);
|
||
|
||
// فاکتورهای دستی (صدور شده توسط مدیر)
|
||
Route::get('/invoices', [\App\Http\Controllers\Api\Customer\CustomerInvoiceController::class, 'index']);
|
||
Route::get('/invoices/{invoice}', [\App\Http\Controllers\Api\Customer\CustomerInvoiceController::class, 'show']);
|
||
Route::post('/invoices/{invoice}/pay', [\App\Http\Controllers\Api\Customer\CustomerInvoiceController::class, 'pay'])
|
||
->middleware('throttle:wallet');
|
||
|
||
// لیست کشورها (عمومی - بدون نیاز به احراز هویت)
|
||
// این endpoint در گروه auth:sanctum نیست
|
||
|
||
// سفارشات
|
||
Route::get('/orders', [CustomerOrderController::class, 'index']);
|
||
Route::post('/orders', [CustomerOrderController::class, 'store']);
|
||
Route::get('/orders/{shipment}', [CustomerOrderController::class, 'show']);
|
||
Route::post('/orders/{shipment}/cancel', [CustomerOrderController::class, 'cancel']);
|
||
Route::post('/orders/{shipment}/resubmit', [CustomerOrderController::class, 'resubmit']);
|
||
|
||
// دانلود PDFهای سفارش
|
||
Route::get('/orders/{shipment}/pdf/awb', [ShipmentPdfController::class, 'awb']);
|
||
Route::get('/orders/{shipment}/pdf/invoice', [ShipmentPdfController::class, 'invoice']);
|
||
Route::get('/orders/{shipment}/pdf/label', [ShipmentPdfController::class, 'label']);
|
||
Route::get('/orders/{shipment}/pdf/import-invoice', [ShipmentPdfController::class, 'importInvoice']);
|
||
|
||
// تعهدنامهها
|
||
Route::get('/orders/{shipment}/commitment-forms', [CommitmentFormController::class, 'shipmentForms']);
|
||
Route::post('/orders/{shipment}/commitment-forms/{form}/upload', [CommitmentFormController::class, 'uploadSigned']);
|
||
Route::get('/orders/{shipment}/commitment-forms/{form}/download', [CommitmentFormController::class, 'downloadSigned'])
|
||
->name('customer.commitment-forms.download');
|
||
// دانلود قالب خام (محافظتشده با auth به جای symlink عمومی)
|
||
Route::get('/orders/{shipment}/commitment-forms/{form}/template', [CommitmentFormController::class, 'downloadTemplate'])
|
||
->name('customer.commitment-forms.template');
|
||
// نوتیفیکیشنها
|
||
Route::get('/notifications', [CustomerOrderController::class, 'notifications']);
|
||
Route::post('/notifications/{notification}/read', [CustomerOrderController::class, 'markNotificationRead']);
|
||
|
||
// پرداخت سفارش
|
||
Route::post('/orders/{shipment}/pay-wallet', [CustomerOrderController::class, 'payFromWallet'])
|
||
->middleware('throttle:wallet');
|
||
Route::post('/orders/{shipment}/pay-gateway', [CustomerOrderController::class, 'payViaGateway'])
|
||
->middleware('throttle:wallet');
|
||
});
|
||
|
||
// ─── Staff Orders API (تأیید سفارشات) ───
|
||
Route::middleware([StaffApiMiddleware::class, 'throttle:staff'])
|
||
->prefix('staff')
|
||
->group(function () {
|
||
|
||
Route::get(
|
||
'/orders/pending-approval',
|
||
[StaffOrderController::class, 'pendingApproval']
|
||
);
|
||
|
||
Route::post(
|
||
'/orders/{shipment}/request-changes',
|
||
[StaffOrderController::class, 'requestChanges']
|
||
);
|
||
|
||
Route::post(
|
||
'/orders/{shipment}/approve',
|
||
[StaffOrderController::class, 'approve']
|
||
);
|
||
|
||
Route::post(
|
||
'/orders/{shipment}/reject',
|
||
[StaffOrderController::class, 'reject']
|
||
);
|
||
|
||
// ─── Customer Financial API ───
|
||
Route::get(
|
||
'/customers/search',
|
||
[CustomerFinancialController::class, 'search']
|
||
);
|
||
|
||
Route::get(
|
||
'/customers/{customer}/financial-status',
|
||
[CustomerFinancialController::class, 'financialStatus']
|
||
);
|
||
});
|
||
});
|
||
|
||
// ══════════════════════════════════════════════════════════════
|
||
// Mock Gateway Routes (عمومی - برای شبیهسازی درگاه)
|
||
// ══════════════════════════════════════════════════════════════
|
||
Route::prefix('v1/payment')->group(function () {
|
||
Route::get('/mock-gateway', [MockGatewayController::class, 'showGateway']);
|
||
Route::get('/mock-gateway/success', [MockGatewayController::class, 'simulateSuccess']);
|
||
Route::get('/mock-gateway/failure', [MockGatewayController::class, 'simulateFailure']);
|
||
});
|
||
|
||
// Callback از درگاه (بدون auth)
|
||
Route::any('/v1/payment/callback', [PaymentController::class, 'callback'])
|
||
->name('payment.callback')
|
||
->middleware('throttle:public');
|
||
|
||
// ══════════════════════════════════════════════════════════════
|
||
// APIهای عمومی (بدون auth) — فقط استعلام قیمت همگانی است
|
||
// ══════════════════════════════════════════════════════════════
|
||
Route::post('/v1/calculate', [PricingController::class, 'calculate'])
|
||
->middleware('throttle:public');
|
||
|
||
// API تأیید موبایل (عمومی)
|
||
Route::post('/v1/verify/send-code', [MobileVerificationController::class, 'sendCode'])
|
||
->middleware('throttle:sms'); // ۱/min per phone
|
||
Route::post('/v1/verify/check-code', [MobileVerificationController::class, 'checkCode'])
|
||
->middleware('throttle:auth'); // ۵/min per IP
|
||
|
||
// API ارسال پیامک (نیاز به auth دارد)
|
||
Route::middleware(['auth:sanctum'])->post('/v1/verify/send-sms', [MobileVerificationController::class, 'sendSms']); |