feat(integration): 完善外部 API 對接邏輯與安全性

1. 新增 API Rate Limiting (每分鐘 60 次)
2. 實作 ProductServiceInterface 與 findOrCreateWarehouseByName 解決跨模組耦合問題
3. 強化 OrderSync API 驗證 (price 欄位限制最小 0、payment_method 加上允許白名單)
4. 實作 OrderSync API 冪等性處理,重複訂單直接回傳現有資訊
5. 修正 ProductSync API 同步邏輯,每次同步皆會更新產品分類與單位
6. 完善 integration API 對接手冊內容與 UI 排版
This commit is contained in:
2026-02-23 10:10:03 +08:00
parent 29cdf37b71
commit a05acd96dc
13 changed files with 303 additions and 37 deletions

View File

@@ -131,4 +131,12 @@ interface InventoryServiceInterface
* @return array
*/
public function getDashboardStats(): array;
/**
* 依倉庫名稱查找或建立倉庫(供外部整合用)。
*
* @param string $warehouseName
* @return object
*/
public function findOrCreateWarehouseByName(string $warehouseName);
}

View File

@@ -0,0 +1,25 @@
<?php
namespace App\Modules\Inventory\Contracts;
/**
* 產品服務介面 供跨模組使用(如 Integration 模組)。
*/
interface ProductServiceInterface
{
/**
* 透過外部 POS ID 進行產品新增或更新Upsert
*
* @param array $data
* @return object
*/
public function upsertFromPos(array $data);
/**
* 透過外部 POS ID 查找產品。
*
* @param string $externalPosId
* @return object|null
*/
public function findByExternalPosId(string $externalPosId);
}

View File

@@ -4,13 +4,16 @@ namespace App\Modules\Inventory;
use Illuminate\Support\ServiceProvider;
use App\Modules\Inventory\Contracts\InventoryServiceInterface;
use App\Modules\Inventory\Contracts\ProductServiceInterface;
use App\Modules\Inventory\Services\InventoryService;
use App\Modules\Inventory\Services\ProductService;
class InventoryServiceProvider extends ServiceProvider
{
public function register(): void
{
$this->app->bind(InventoryServiceInterface::class, InventoryService::class);
$this->app->bind(ProductServiceInterface::class, ProductService::class);
}
public function boot(): void

View File

@@ -590,5 +590,22 @@ class InventoryService implements InventoryServiceInterface
'abnormalItems' => $abnormalItems,
];
}
}
/**
* 依倉庫名稱查找或建立倉庫(供外部整合用)。
*
* @param string $warehouseName
* @return Warehouse
*/
public function findOrCreateWarehouseByName(string $warehouseName)
{
return Warehouse::firstOrCreate(
['name' => $warehouseName],
[
'code' => 'SALES-' . strtoupper(bin2hex(random_bytes(4))),
'type' => 'system_sales',
'is_active' => true,
]
);
}
}

View File

@@ -2,13 +2,14 @@
namespace App\Modules\Inventory\Services;
use App\Modules\Inventory\Contracts\ProductServiceInterface;
use App\Modules\Inventory\Models\Product;
use App\Modules\Inventory\Models\Category;
use App\Modules\Inventory\Models\Unit;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Log;
class ProductService
class ProductService implements ProductServiceInterface
{
/**
* Upsert product from external POS source.
@@ -45,8 +46,8 @@ class ProductService
$product->code = $data['code'] ?? $product->external_pos_id;
}
// Handle Category (Default: 未分類)
if (empty($product->category_id)) {
// Handle Category — 每次同步都更新(若有傳入)
if (!empty($data['category']) || empty($product->category_id)) {
$categoryName = $data['category'] ?? '未分類';
$category = Category::firstOrCreate(
['name' => $categoryName],
@@ -55,8 +56,8 @@ class ProductService
$product->category_id = $category->id;
}
// Handle Base Unit (Default: 個)
if (empty($product->base_unit_id)) {
// Handle Base Unit — 每次同步都更新(若有傳入)
if (!empty($data['unit']) || empty($product->base_unit_id)) {
$unitName = $data['unit'] ?? '個';
$unit = Unit::firstOrCreate(['name' => $unitName]);
$product->base_unit_id = $unit->id;
@@ -69,4 +70,15 @@ class ProductService
return $product;
});
}
/**
* 透過外部 POS ID 查找產品。
*
* @param string $externalPosId
* @return Product|null
*/
public function findByExternalPosId(string $externalPosId)
{
return Product::where('external_pos_id', $externalPosId)->first();
}
}