# 通知文檔
## 通知類型
| 類型 | 編號 |
|------------|----|
| [訂單已配對司機](#訂單已配對司機) | 0 |
| [司機已接單](#司機已接單) | 1 |
| [配對訂單已被乘客取消](#配對訂單已被乘客取消) | 2 |
| [訂單已完成](#訂單已完成) | 3 |
| [配對訂單超時](#配對訂單超時) | 4 |
| [訂單已被運營人員取消](#訂單已被運營人員取消) | 5 |
## 通知資料欄位意義
### 訂單已配對司機
| key | 意義 | 類型 |
|-------------------|---------|----------------|
| no_type | 通知類型 | string |
| no_type_id | 通知編號 | int |
| driver_name | 司機名稱 | string or null |
| passenger_name | 乘客名稱 | string |
| order_id | 訂單 id | string |
| pick_up_location | 上車地點 | string or null |
| pick_up_lat | 上車緯度 | float or null |
| pick_up_lng | 上車經度 | float or null |
| pick_up_remark | 上車備注 | string or null |
| drop_off_location | 下車地點 | float or null |
| drop_off_lat | 下車緯度 | float or null |
| drop_off_lng | 下車經度 | string or null |
| payment_method | 付款方式 | string or null |
| accepted_charge | 可接受收費標準 | string |
| passenger_count | 乘客人數 | int |
| remark | 備注 | string or null |
| add_money | 加錢 | float |
| tips | 小費 | float |
### 司機已接單
| key | 意義 | 類型 |
|-------------------|---------|----------------|
| no_type | 通知類型 | string |
| no_type_id | 通知編號 | int |
| driver_name | 司機名稱 | string or null |
| passenger_name | 乘客名稱 | string |
| order_id | 訂單 id | string |
| pick_up_location | 上車地點 | string or null |
| pick_up_lat | 上車緯度 | float or null |
| pick_up_lng | 上車經度 | float or null |
| pick_up_remark | 上車備注 | string or null |
| drop_off_location | 下車地點 | float or null |
| drop_off_lat | 下車緯度 | float or null |
| drop_off_lng | 下車經度 | string or null |
| payment_method | 付款方式 | string or null |
| accepted_charge | 可接受收費標準 | string |
| passenger_count | 乘客人數 | int |
| remark | 備注 | string or null |
| add_money | 加錢 | float |
| tips | 小費 | float |
### 配對訂單已被乘客取消
| key | 意義 | 類型 |
|-------------------|---------|----------------|
| no_type | 通知類型 | string |
| no_type_id | 通知編號 | int |
| driver_name | 司機名稱 | string or null |
| passenger_name | 乘客名稱 | string |
| order_id | 訂單 id | string |
| pick_up_location | 上車地點 | string or null |
| pick_up_lat | 上車緯度 | float or null |
| pick_up_lng | 上車經度 | float or null |
| pick_up_remark | 上車備注 | string or null |
| drop_off_location | 下車地點 | float or null |
| drop_off_lat | 下車緯度 | float or null |
| drop_off_lng | 下車經度 | string or null |
| payment_method | 付款方式 | string or null |
| accepted_charge | 可接受收費標準 | string |
| passenger_count | 乘客人數 | int |
| remark | 備注 | string or null |
| add_money | 加錢 | float |
| tips | 小費 | float |
### 訂單已完成
| key | 意義 | 類型 |
|-------------------|---------|----------------|
| no_type | 通知類型 | string |
| no_type_id | 通知編號 | int |
| driver_name | 司機名稱 | string or null |
| passenger_name | 乘客名稱 | string |
| order_id | 訂單 id | string |
| pick_up_location | 上車地點 | string or null |
| pick_up_lat | 上車緯度 | float or null |
| pick_up_lng | 上車經度 | float or null |
| pick_up_remark | 上車備注 | string or null |
| drop_off_location | 下車地點 | float or null |
| drop_off_lat | 下車緯度 | float or null |
| drop_off_lng | 下車經度 | string or null |
| payment_method | 付款方式 | string or null |
| accepted_charge | 可接受收費標準 | string |
| passenger_count | 乘客人數 | int |
| remark | 備注 | string or null |
| add_money | 加錢 | float |
| tips | 小費 | float |
### 配對訂單超時
| key | 意義 | 類型 |
|-------------------|---------|----------------|
| no_type | 通知類型 | string |
| no_type_id | 通知編號 | int |
| driver_name | 司機名稱 | string or null |
| passenger_name | 乘客名稱 | string |
| order_id | 訂單 id | string |
| pick_up_location | 上車地點 | string or null |
| pick_up_lat | 上車緯度 | float or null |
| pick_up_lng | 上車經度 | float or null |
| pick_up_remark | 上車備注 | string or null |
| drop_off_location | 下車地點 | float or null |
| drop_off_lat | 下車緯度 | float or null |
| drop_off_lng | 下車經度 | string or null |
| payment_method | 付款方式 | string or null |
| accepted_charge | 可接受收費標準 | string |
| passenger_count | 乘客人數 | int |
| remark | 備注 | string or null |
| add_money | 加錢 | float |
| tips | 小費 | float |
### 訂單已被運營人員取消
| key | 意義 | 類型 |
|-------------------|---------|----------------|
| no_type | 通知類型 | string |
| no_type_id | 通知編號 | int |
| driver_name | 司機名稱 | string or null |
| passenger_name | 乘客名稱 | string |
| order_id | 訂單 id | string |
| pick_up_location | 上車地點 | string or null |
| pick_up_lat | 上車緯度 | float or null |
| pick_up_lng | 上車經度 | float or null |
| pick_up_remark | 上車備注 | string or null |
| drop_off_location | 下車地點 | float or null |
| drop_off_lat | 下車緯度 | float or null |
| drop_off_lng | 下車經度 | string or null |
| payment_method | 付款方式 | string or null |
| accepted_charge | 可接受收費標準 | string |
| passenger_count | 乘客人數 | int |
| remark | 備注 | string or null |
| add_money | 加錢 | float |
| tips | 小費 | float |
# 訂單聊天室整合指南
本文檔針對第三方系統或前端團隊,說明如何串接訂單聊天室的 REST API 與即時訊息(WebSocket / Broadcast)。範例皆以 Bearer Token 驗證為主;亦可使用同源的 session cookie(視服務端部署而定)。
## 概覽
- REST API:用於發送訊息與取得歷史訊息。呼叫後伺服器會將訊息持久化並觸發即時 broadcast。
- 即時訊息:伺服器會 broadcast 到 private channel(頻道格式:`order.{order_id}`),已授權的使用者可以訂閱並收到新訊息事件。
------------------------------------------------------------------------
## 認證(Authentication)
- REST API:在 HTTP header 中帶入 `Authorization: Bearer <TOKEN>`(或採用同源 session cookie)。
- 頻道授權(private channel):前端在建立 WebSocket 或 Echo 時,會由 client 向 `/broadcasting/auth` 發出授權請求,此請求也需要攜帶相同的驗證(Authorization header 或 cookie)。
範例 header:
```
Authorization: Bearer <YOUR_TOKEN>
Accept: application/json
Content-Type: application/json
```
------------------------------------------------------------------------
## REST API - 端點說明
Base URL 範例:`{BASE_URL}`(請替換成貴方環境)
### 1) 發送訊息
- 方法:POST
- 路徑:`/api/order/chatroom/sent`
- 必要 Header:`Authorization: Bearer <TOKEN>`、`Accept: application/json`
- Body (JSON):
```json
{
"order_id": "ORD12345",
"message": "我到了,請下樓。"
}
```
- 行為:伺服器會驗證 `order_id` 與當前使用者是否有權存取該訂單聊天室(例如乘客或司機),若合法則:
1. 觸發即時事件(broadcast 到 private channel `order.{order_id}`)
2. 將訊息寫入資料庫(持久化)
- 成功回應 (HTTP 200):
```json
{
"success": true,
"message": "訊息已成功發送",
"data": []
}
```
- 常見錯誤:
- 401 Unauthorized:未帶 token 或 token 無效
- 422 Unprocessable Entity:參數驗證錯誤(例如 `order_id` 不存在、`message` 違反長度限制)
- 403 Forbidden:使用者無權存取該訂單聊天室
- 500 Internal Server Error:伺服器錯誤
範例 curl:
```bash
curl -X POST "{BASE_URL}/api/order/chatroom/sent" \
-H "Authorization: Bearer $TOKEN" \
-H "Accept: application/json" \
-H "Content-Type: application/json" \
-d '{"order_id":"ORD12345","message":"我到達集合地點"}'
```
JavaScript fetch 範例:
```javascript
await fetch(`${BASE_URL}/api/order/chatroom/sent`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Accept': 'application/json',
'Authorization': 'Bearer ' + token,
},
body: JSON.stringify({ order_id: 'ORD12345', message: '我到了' })
});
```
### 2) 取得聊天室歷史
- 方法:GET
- 路徑:`/api/order/chatroom/history`
- 參數(Query):`order_id`(必填)
- 必要 Header:`Authorization: Bearer <TOKEN>`
- 成功回應 (HTTP 200):
```json
{
"success": true,
"message": "取得聊天室歷史資料成功",
"data": [
{
"id": 123,
"order_id": "ORD12345",
"user_id": 45,
"message": "您好",
"role": 0,
"created_at": "2025-09-23T15:30:00Z",
"user": { "id":45, "name":"乘客A" }
}
]
}
```
範例 curl:
```bash
curl -G "{BASE_URL}/api/order/chatroom/history" \
-H "Authorization: Bearer $TOKEN" \
--data-urlencode "order_id=ORD12345"
```
備註:回傳的 `data` 為訊息陣列(時間排序依服務端實作可能為新->舊或舊->新),請在介面上處理排序與分頁(若必要)。
------------------------------------------------------------------------
## 即時訊息(WebSocket / Broadcast)整合
### 頻道與事件
- **頻道名稱(Private Channel)**:`order.{order_id}`(例如 `order.ORD12345`)
- **事件名稱**:`OrderMessageSentEvent`(依廣播實作,實際前端收到的 event 名稱可能為短名或包含 namespace,但使用 `OrderMessageSentEvent` 應可接收)
- **事件載荷(payload)**:伺服器會送出 JSON,通常包含下列欄位(請以實際收到的 payload 為準):
```json
{
"order": { "order_id": "ORD12345", "id": 987 },
"sender": { "id": 45, "name": "乘客A" },
"role": 0,
"message": "我在路口,請下樓"
}
```
說明:`role` 通常為數字(0 表示乘客、1 表示司機)。`sender` 是發送者(簡化的 user 資料)。
### 訂閱流程(以 Laravel Echo + Pusher/laravel-websockets 為例)
1. 建立 Echo 並在 auth headers 中帶入 Bearer token(或使用同源 cookie):
```javascript
import Echo from 'laravel-echo';
import Pusher from 'pusher-js';
window.Pusher = Pusher;
window.Echo = new Echo({
broadcaster: 'pusher',
key: '<PUSHER_KEY>',
wsHost: window.location.hostname,
wsPort: 6001,
forceTLS: false,
disableStats: true,
authEndpoint: '/broadcasting/auth',
auth: {
headers: {
Authorization: 'Bearer ' + token,
Accept: 'application/json',
}
}
});
```
2. 訂閱 private channel 並監聽事件:
```javascript
const orderId = 'ORD12345';
Echo.private(`order.${orderId}`)
.listen('OrderMessageSentEvent', (e) => {
// e.message, e.sender, e.role, e.order
console.log('收到聊天室訊息', e);
});
```
若使用 socket.io 或其他 broadcaster,初始化方式會不同,但重點是:
- 必須訂閱 private channel `order.{order_id}`
- 訂閱前會向 `/broadcasting/auth` 要求授權,請確保此 request 帶入正確的認證資訊
### 授權失敗處理
- 若 `/broadcasting/auth` 回傳 401/403,表示 client 未通過授權,無法訂閱 private channel。請確認:
- token 是否正確
- token 是否有對應使用者且該使用者為該訂單的乘客或司機(或為被允許的 matched 身分)
範例授權失敗 response(HTTP 401/403):
```json
{ "message": "Unauthenticated." }
```
或
```json
{ "message": "This action is unauthorized." }
```
### 事件接收注意事項
- 伺服器 broadcast 的 payload 可能包含模型的部分欄位,請在接收端依需要映射或過濾。
- 建議實做「樂觀 UI」:先在 client 端顯示暫存訊息(pending),待 POST API 回傳成功或收到 broadcast 回覆後將狀態調整為已送達。
- 不要只依賴 socket 訊息來持久化訊息(例如如果 client 直接把訊息送到 socket 而非 POST API,可能會無法持久化或繞過後端驗證)。
------------------------------------------------------------------------
## 錯誤類型與範例處理
- 422 Validation Error 範例(Laravel 典型格式):
```json
{
"message": "The given data was invalid.",
"errors": {
"order_id": ["使用者無權限存取此訂單聊天室。"],
"message": ["The message field is required."]
}
}
```
- 401/403:請提示使用者重新登入或聯繫後端以取得存取權限。