346 lines
12 KiB
Markdown
346 lines
12 KiB
Markdown
# 第三方系統 API 對接手冊
|
||
|
||
Star ERP 系統提供外部整合 API (Integration API) 供電商前台、POS 機或其他第三方系統串接。
|
||
所有的整合 API 均受到 Laravel Sanctum Token 與多租戶 (Multi-tenant) Middleware 保護。
|
||
|
||
## 基礎連線資訊
|
||
|
||
- **API Base URL**: `https://[租戶網域]/api/v1/integration` (依實際部署網址為準,單機開發為 `http://localhost/api/v1/integration`)
|
||
- **Headers 要求**:
|
||
- `Accept: application/json`
|
||
- `Content-Type: application/json`
|
||
- `Authorization: Bearer {YOUR_API_TOKEN}` (由 ERP 系統管理員核發的 Sanctum Token)
|
||
- **速率限制**:每位使用者每分鐘最多 60 次請求。超過時會回傳 `429 Too Many Requests`。
|
||
|
||
---
|
||
|
||
## 1. 商品資料讀取 (Product Retrieval)
|
||
|
||
此 API 用於讓外部系統(如 POS)依據關鍵字、分類或最後更新時間,從 ERP 中批量抓取商品資料。支援分頁與增量同步。
|
||
|
||
- **Endpoint**: `/products`
|
||
- **Method**: `GET`
|
||
|
||
### Query Parameters
|
||
|
||
| 參數名稱 | 類型 | 必填 | 說明 |
|
||
| :--- | :--- | :---: | :--- |
|
||
| `product_id` | Integer| 否 | 依 ERP 商品 ID (`products.id`) 精準篩選 |
|
||
| `external_pos_id` | String | 否 | 依外部 POS 端的唯一識別碼 (`external_pos_id`) 精準篩選 |
|
||
| `barcode` | String | 否 | 依商品條碼 (Barcode) 精準篩選 |
|
||
| `code` | String | 否 | 依商品代碼 (Code) 精準篩選 |
|
||
| `category` | String | 否 | 分類名稱精準過濾 |
|
||
| `updated_after` | String | 否 | 增量同步機制。僅回傳該時間點之後有異動的商品 (格式: `YYYY-MM-DD HH:mm:ss`) |
|
||
| `per_page` | Integer| 否 | 每頁筆數 (預設 50, 最大 100) |
|
||
| `page` | Integer| 否 | 分頁頁碼 (預設 1) |
|
||
|
||
### Response
|
||
|
||
**Success (HTTP 200)**
|
||
僅開放公開價格 `price`,隱藏敏感成本與會員價。
|
||
```json
|
||
{
|
||
"status": "success",
|
||
"data": [
|
||
{
|
||
"id": 12,
|
||
"code": "PROD-001",
|
||
"barcode": "4710001",
|
||
"name": "可口可樂 600ml",
|
||
"external_pos_id": "POS-P-001",
|
||
"category_name": "飲品",
|
||
"brand": "可口可樂",
|
||
"specification": "600ml",
|
||
"unit_name": "瓶",
|
||
"price": 25.0,
|
||
"is_active": true,
|
||
"updated_at": "2026-03-19 09:30:00"
|
||
}
|
||
],
|
||
"meta": {
|
||
"current_page": 1,
|
||
"last_page": 5,
|
||
"per_page": 50,
|
||
"total": 240
|
||
}
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## 2. 商品資料同步 (Upsert Product)
|
||
|
||
此 API 用於將第三方系統(如 POS)的產品資料單向同步至 ERP。採用 Upsert 邏輯:若 `external_pos_id` 存在則更新資料,不存在則新增產品。
|
||
|
||
- **Endpoint**: `/products/upsert`
|
||
- **Method**: `POST`
|
||
|
||
### Request Body (JSON)
|
||
|
||
| 參數名稱 | 類型 | 必填 | 說明 |
|
||
| :--- | :--- | :---: | :--- |
|
||
| `external_pos_id` | String | **是** | 在 POS 系統中的唯一商品 ID (Primary Key) |
|
||
| `name` | String | **是** | 商品名稱 (最大 255 字元) |
|
||
| `category` | String | **是** | 商品分類名稱。若系統中不存在則自動建立 (最大 100 字元) |
|
||
| `unit` | String | **是** | 商品單位 (例如:個、杯、件)。若不存在則自動建立 (最大 100 字元) |
|
||
| `code` | String | 否 | 商品代碼。若未提供將由 ERP 自動產生 (最大 100 字元) |
|
||
| `price` | Decimal | 否 | 商品售價 (預設 0) |
|
||
| `barcode` | String | 否 | 商品條碼 (最大 100 字元)。若未提供將由 ERP 自動產生 |
|
||
| `brand` | String | 否 | 商品品牌名稱 (最大 100 字元) |
|
||
| `specification` | String | 否 | 商品規格描述 (最大 255 字元) |
|
||
| `cost_price` | Decimal | 否 | 成本價 (預設 0) |
|
||
| `member_price` | Decimal | 否 | 會員價 (預設 0) |
|
||
| `wholesale_price` | Decimal | 否 | 批發價 (預設 0) |
|
||
| `is_active` | Boolean| 否 | 是否上架/啟用 (預設 true) |
|
||
| `updated_at` | String | 否 | POS 端的最後更新時間 (格式: YYYY-MM-DD HH:mm:ss) |
|
||
|
||
**請求範例:**
|
||
```json
|
||
{
|
||
"external_pos_id": "POS-PROD-9001",
|
||
"name": "特級冷壓初榨橄欖油 500ml",
|
||
"category": "調味料",
|
||
"unit": "瓶",
|
||
"price": 380.00,
|
||
"barcode": "4711234567890",
|
||
"brand": "健康王",
|
||
"specification": "500ml / 玻璃瓶裝",
|
||
"cost_price": 250.00,
|
||
"member_price": 350.00,
|
||
"wholesale_price": 300.00,
|
||
"is_active": true,
|
||
"updated_at": "2024-03-15 14:30:00"
|
||
}
|
||
```
|
||
|
||
> [!TIP]
|
||
> **自動編碼與分類機制**:
|
||
> - **必填項**:`category` 與 `unit` 為必填。若 ERP 中無對應名稱,將會依據傳入值自動建立。
|
||
> - **自動編碼**:若未提供 `code` (商品代碼),將由 ERP 自動產生 8 位隨機代碼。
|
||
> - **自動條碼**:若未提供 `barcode` (條碼),將由 ERP 自動產生 13 位隨機數字條碼。
|
||
> - 建議在同步後儲存回傳的 `id`、`code` 與 `barcode`,以利後續精確對接。
|
||
|
||
### Response
|
||
|
||
回傳 ERP 端的完整商品主檔資訊,供外部系統回存 ID 或代碼。
|
||
|
||
### 回傳範例 (Success)
|
||
- **Status Code**: `200 OK`
|
||
```json
|
||
{
|
||
"message": "Product synced successfully",
|
||
"data": {
|
||
"id": 1,
|
||
"external_pos_id": "POS-P-999",
|
||
"code": "A1B2C3D4",
|
||
"barcode": "4710009990001"
|
||
}
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## 3. 門市庫存查詢 (Query Inventory)
|
||
|
||
此 API 用於讓外部系統(如 POS)依據特定的「倉庫代碼」,查詢該倉庫目前所有商品的庫存餘額。
|
||
**注意**:此 API 會回傳該倉庫內的所有商品數量,不論該商品是否已綁定外部 POS ID。
|
||
|
||
- **Endpoint**: `/inventory/{warehouse_code}`
|
||
- **Method**: `GET`
|
||
|
||
### URL 路徑參數
|
||
|
||
| 參數名稱 | 類型 | 必填 | 說明 |
|
||
| :--- | :--- | :---: | :--- |
|
||
| `warehouse_code` | String | **是** | 要查詢的倉庫代碼 (例如:`STORE-001`,測試可使用預設建立之 `api-test-01`) |
|
||
|
||
### Query Parameters (選填)
|
||
|
||
| 參數名稱 | 類型 | 說明 |
|
||
| :--- | :--- | :--- |
|
||
| `product_id` | String | 依 ERP 商品 ID (`products.id`) 篩選。 |
|
||
| `external_pos_id` | String | 依外部 POS 端的唯一識別碼 (`external_pos_id`) 篩選。 |
|
||
| `barcode` | String | 依商品條碼 (Barcode) 篩選商品。 |
|
||
| `code` | String | 依商品代碼 (Code) 篩選商品。 |
|
||
|
||
若不帶任何參數,將回傳該倉庫下所有商品的庫存餘額。
|
||
|
||
### Response
|
||
|
||
**Success (HTTP 200)**
|
||
回傳該倉庫內所有的商品目前庫存總數及詳細資訊。若商品未建置 `external_pos_id`,該欄位將顯示為 `null`。
|
||
```json
|
||
{
|
||
"status": "success",
|
||
"warehouse_code": "api-test-01",
|
||
"data": [
|
||
{
|
||
"product_id": 1,
|
||
"external_pos_id": "PROD-001",
|
||
"product_code": "PROD-A001",
|
||
"product_name": "特級冷壓初榨橄欖油 500ml",
|
||
"barcode": "4710123456789",
|
||
"category_name": "調味料",
|
||
"unit_name": "瓶",
|
||
"price": 450.00,
|
||
"brand": "奧利塔",
|
||
"specification": "500ml/瓶",
|
||
"batch_number": "PROD-A001-TW-20231026-01",
|
||
"expiry_date": "2025-10-26",
|
||
"quantity": 15
|
||
},
|
||
{
|
||
"external_pos_id": null,
|
||
"product_code": "MAT-001",
|
||
"product_name": "未包裝干貝醬原料",
|
||
"barcode": null,
|
||
"category_name": "原料",
|
||
"unit_name": "kg",
|
||
"price": 0.00,
|
||
"brand": null,
|
||
"specification": null,
|
||
"batch_number": "MAT-001-TW-20231020-01",
|
||
"expiry_date": "2024-04-20",
|
||
"quantity": 2.5
|
||
}
|
||
]
|
||
}
|
||
```
|
||
|
||
**Error: Warehouse Not Found (HTTP 404)**
|
||
當傳入系統中不存在的倉庫代碼時發生。
|
||
```json
|
||
{
|
||
"status": "error",
|
||
"message": "Warehouse with code 'STORE-999' not found."
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## 4. 訂單資料寫入與扣庫 (Create Order)
|
||
|
||
此 API 用於讓第三方系統(如 POS 結帳後)將「已成交」的訂單推送到 ERP。
|
||
**重要提醒**:寫入訂單時,ERP 會無條件扣除庫存。若指定的「批號」庫存不足,系統會自動轉向 `NO-BATCH` 庫存項目扣除;若最終仍不足,則會在 `NO-BATCH` 產生負數庫存。
|
||
|
||
- **Endpoint**: `/orders`
|
||
- **Method**: `POST`
|
||
|
||
### Request Body (JSON)
|
||
|
||
| 欄位名稱 | 型態 | 必填 | 說明 |
|
||
| :--- | :--- | :---: | :--- |
|
||
| `external_order_id` | String | **是** | 第三方系統中的唯一訂單編號,不可重複 (Unique) |
|
||
| `name` | String | **是** | 訂單名稱或客戶名稱 (最多 255 字元) |
|
||
| `warehouse_code` | String | **是** | 指定扣除庫存的倉庫代碼 (例如:`api-test-01` 測試倉)。若找不到對應倉庫將直接拒絕請求 |
|
||
| `payment_method` | String | 否 | 付款方式,僅接受:`cash`, `credit_card`, `line_pay`, `ecpay`, `transfer`, `other`。預設為 `cash` |
|
||
| `total_amount` | Number | **是** | 整筆訂單的總交易金額 (例如:500) |
|
||
| `total_qty` | Number | **是** | 整筆訂單的商品總數量 (例如:5) |
|
||
| `sold_at` | String(Date) | 否 | 交易發生時間,預設為當下時間 (格式: YYYY-MM-DD HH:mm:ss) |
|
||
| `items` | Array | **是** | 訂單明細陣列,至少需包含一筆商品 |
|
||
|
||
#### `items` 陣列欄位說明:
|
||
|
||
| 欄位名稱 | 型態 | 必填 | 說明 |
|
||
| :--- | :--- | :---: | :--- |
|
||
| `product_id` | Integer | **是** | **ERP 內部的商品 ID (`id`)**。請優先使用商品同步 API 取得此 ID |
|
||
| `batch_number` | String | 否 | **商品批號**。若提供,將優先扣除該批號庫存;若該批號無剩餘或找不到,將自動 fallback 至 `NO-BATCH` 扣除 |
|
||
| `qty` | Number | **是** | 銷售數量 (必須 > 0) |
|
||
| `price` | Number | **是** | 銷售單價 |
|
||
|
||
**注意**:請確保傳入正確的 `product_id` 以便 ERP 準確識別商品與扣除庫存。
|
||
|
||
**請求範例:**
|
||
```json
|
||
{
|
||
"external_order_id": "ORD-20231024-001",
|
||
"name": "陳小明-干貝醬訂購",
|
||
"warehouse_code": "STORE-01",
|
||
"payment_method": "credit_card",
|
||
"total_amount": 1050,
|
||
"total_qty": 3,
|
||
"sold_at": "2023-10-24 15:30:00",
|
||
"items": [
|
||
{
|
||
"product_id": 15,
|
||
"batch_number": "BATCH-2024-A1",
|
||
"qty": 2,
|
||
"price": 500
|
||
}
|
||
]
|
||
}
|
||
```
|
||
|
||
### Response
|
||
|
||
**Success (HTTP 201)**
|
||
```json
|
||
{
|
||
"message": "Order synced and stock deducted successfully",
|
||
"order_id": 42
|
||
}
|
||
```
|
||
|
||
#### 錯誤回應 (HTTP 422 Unprocessable Entity - 驗證失敗)
|
||
|
||
當傳入資料格式有誤、商品 ID 於系統中不存在(如 `items` 內傳入了無法辨識的商品 ID),或是倉庫代碼無效時返回。
|
||
|
||
- **`message`** (字串): 錯誤摘要。
|
||
- **`errors`** (物件): 具體的錯誤明細列表,能一次性回報多個商品不存在或其它欄位錯誤。
|
||
|
||
```json
|
||
{
|
||
"message": "Validation failed",
|
||
"errors": {
|
||
"items": [
|
||
"The following product IDs are not found: 15, 22. Please ensure these products exist in the system."
|
||
],
|
||
"warehouse_code": [
|
||
"Warehouse with code STORE-999 not found."
|
||
]
|
||
}
|
||
}
|
||
```
|
||
|
||
#### 錯誤回應 (HTTP 500 Internal Server Error - 伺服器異常)
|
||
|
||
當系統發生預期外的錯誤(如資料庫連線失敗)時返回。
|
||
|
||
```json
|
||
{
|
||
"message": "Sync failed: An unexpected error occurred."
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## 幂等性說明 (Idempotency)
|
||
|
||
訂單 API 支援幂等性處理:若傳入已存在的 `external_order_id`,系統不會報錯,而是回傳該訂單的資訊:
|
||
|
||
```json
|
||
{
|
||
"message": "Order already exists",
|
||
"order_id": 42
|
||
}
|
||
```
|
||
|
||
這讓第三方系統在網路問題導致重送時,不會產生重複訂單或重複扣庫。
|
||
|
||
---
|
||
|
||
## 常見問題與除錯 (FAQ)
|
||
|
||
1. **收到 `401 Unauthorized` 錯誤?**
|
||
- 請檢查請求標頭 (Header) 是否有正確攜帶 `Authorization: Bearer {Token}`。
|
||
- 確認該 Token 尚未過期或被撤銷。
|
||
|
||
2. **收到 `422 Unprocessable Entity` 錯誤?**
|
||
- 代表傳送的 JSON 欄位不符合格式要求,例如必填欄位遺漏、數量為負數、或 `payment_method` 不在允許的值中。Laravel 會在回應的 `errors` 物件中詳細說明哪個欄位驗證失敗。
|
||
|
||
3. **收到 `429 Too Many Requests` 錯誤?**
|
||
- 代表已超過速率限制(每分鐘 60 次),請稍後再試。
|
||
|
||
4. **庫存扣除邏輯**
|
||
- POS 訂單寫入視為「已發生之事實」,系統會無條件扣除庫存。若該產品在指定倉庫原先庫存為 0,訂單寫入後庫存將變為負數,提醒門市人員需進行調撥補貨。
|