Xử lý lỗi
Các loại error, mã lỗi server và cách xử lý trong Go SDK
SDK sử dụng hệ thống error phân cấp. Tất cả error type đều implement interface error. Error con unwrap về error cha, nên hãy luôn dùng errors.As (không dùng type switch) và kiểm tra type cụ thể hơn trước.
Import
Package gốc có tên ssisdk. Dùng alias tường minh:
import (
"errors"
ssisdk "github.com/SSI-Securities-Inc/ssi-sdk-go/v3"
)Danh sách Error
| Error Type | Embed | Mô tả |
|---|---|---|
*ssisdk.SSIError | — | Error cơ sở. Các field: Message, Code (string), StatusCode (int), ResponseBody, Headers |
*ssisdk.AuthenticationError | SSIError | Xác thực thất bại (sai credentials, sai OTP, token hết hạn) |
*ssisdk.SmartOTPPendingError | AuthenticationError | Smart OTP chưa được duyệt (HTTP 202 / mã 401114). Poll lại với cùng transaction id |
*ssisdk.SmartOTPRejectedError | AuthenticationError | Smart OTP bị từ chối, không tìm thấy hoặc thuộc giao dịch khác (401113, 401115, 401116) |
*ssisdk.ReauthenticationRequiredError | AuthenticationError | Phiên không thể refresh nữa. Cần OTP mới; SDK xóa token đang lưu |
*ssisdk.APIError | SSIError | API trả về lỗi — có thêm StatusCode, ResponseBody |
*ssisdk.DuplicateRequestError | APIError | HTTP 409: clientRequestId / batchRequestId đã được dùng trong ngày. SDK không bao giờ tự gửi lại với id mới |
*ssisdk.RateLimitError | SSIError | Vượt giới hạn request (HTTP 429, code RATE_LIMITED). Các field: RetryAfterMs *int, Limit *int, Remaining *int, Reset *float64 |
*ssisdk.WebSocketError | SSIError | Lỗi kết nối hoặc giao tiếp WebSocket |
*ssisdk.TradingWSError | WebSocketError | Server từ chối hoặc báo lỗi một lệnh Trading WebSocket (xem bên dưới). Các field: Status, ErrorCode, ServerMessage |
*ssisdk.ValidationError | SSIError | Lỗi validate input (thường phát sinh tại local, chưa gửi gì đi) |
RetryAfterMs, Limit và Remaining là pointer: bằng nil khi server không gửi giá trị. Reset luôn là nil với REST.
Sử dụng cơ bản
token, err := auth.Authenticate(os.Getenv("SSI_OTP"))
if err != nil {
log.Printf("Error: %v", err)
}Xử lý lỗi chi tiết
result, err := t.Trading.PlaceLimitOrder("YOUR_ACCOUNT_NO", "SSI", trading.OrderSideBuy, 100, 68000)
if err != nil {
var dup *ssisdk.DuplicateRequestError
var rl *ssisdk.RateLimitError
var reauth *ssisdk.ReauthenticationRequiredError
var authErr *ssisdk.AuthenticationError
var apiErr *ssisdk.APIError
var vErr *ssisdk.ValidationError
var wsErr *ssisdk.WebSocketError
switch {
case errors.As(err, &dup):
// 409: đừng đặt lại với id mới trước khi biết lệnh đầu có còn hiệu lực không
log.Printf("Trùng request id: %v", dup)
case errors.As(err, &rl):
if rl.RetryAfterMs != nil {
log.Printf("Rate limited, retry sau %d ms", *rl.RetryAfterMs)
} else {
log.Println("Rate limited")
}
case errors.As(err, &reauth):
log.Println("Phiên hết hạn, cần OTP mới")
case errors.As(err, &authErr):
log.Println("Lỗi xác thực")
case errors.As(err, &apiErr):
log.Printf("API error %d (%s): %s", apiErr.StatusCode, apiErr.Code, apiErr.Message)
case errors.As(err, &vErr):
log.Printf("Validation: %s", vErr.Message)
case errors.As(err, &wsErr):
log.Printf("WebSocket: %s", wsErr.Message)
default:
log.Printf("Unknown error: %v", err)
}
}Vì DuplicateRequestError embed APIError, còn ReauthenticationRequiredError, SmartOTPPendingError, SmartOTPRejectedError embed AuthenticationError, nên case cụ thể phải đặt trước case cha trong switch.
Smart OTP
SmartOTPPendingError là trạng thái bình thường khi người dùng chưa duyệt. Poll với cùng transaction id (EnsureAuthenticated / EnsureAuthenticatedWith tự poll giúp bạn, xem Client Classes). SmartOTPRejectedError và ReauthenticationRequiredError là lỗi kết thúc: hãy yêu cầu OTP mới.
Server Error Codes
ServerErrorCode là named type cho các mã lỗi server đã biết. SDK không bao giờ lỗi vì mã chưa biết: dùng ssisdk.LookupServerErrorCode(code) (trả (ServerErrorCode, bool)) hoặc code.IsKnown() để kiểm tra. Một số tên là alias của cùng một số.
| Nhóm | Tên và mã |
|---|---|
| Auth / account / OTP | ErrCredentialNotLinkedToClient 400101, ErrTwoFAInfoError 400102, ErrTwoFAInfoMissing 400103, ErrOTPSendFailed / ErrSymbolRequired 400104, ErrPushApprovalCreateFailed 400171, ErrOTPVerificationFailed 401100, ErrRefreshTokenInvalid 401101, ErrRefreshClientMissing 401102, ErrRefreshTokenRevokedOrExpired 401103, ErrRefreshAPIKeyGone 401104, ErrInvalidAPICredential / ErrTokenWithoutOTP 401105, ErrPushApprovalNotFound 401113, ErrPushApprovalPending / ErrSmartOTPPending 401114, ErrPushApprovalRejected 401115, ErrPushApprovalForeignTransaction 401116, ErrNoScopeGranted 403100 |
| Signature | ErrSignatureMissing 401006, ErrSignatureBodyEmpty 401008, ErrSignatureMismatch / ErrSignatureInvalid 401013 |
| Trading | ErrModifyPriceAndQuantity 400107, ErrReplayWindowExceeded 400111 |
| Market validators | ErrSymbolInvalidCharacters 400006, ErrFromAfterTo 400202, ErrBoardNotAllowed 400208, ErrTimeframeNotSupported 400210, ErrDateFormatInvalid 400213, ErrIndexSummaryNeedsOneOf 504207, ErrSecuritiesInfoNeedsOneOf 504208, ErrSecuritiesSummaryNeedsSymbolXorIndex 504209 |
Các mã 401101-401104 và 401106-401108 khiến SDK ngừng refresh và yêu cầu xác thực lại (ReauthenticationRequiredError).
Batch Order Errors
Lỗi của PlaceBatchOrders / CancelBatchOrders (chi tiết trong Batch Orders):
| Error | Khi nào | Cần làm |
|---|---|---|
*ssisdk.ValidationError | Batch rỗng, vượt Config.MaxBatchOrders, một lệnh sai dữ liệu, ClientRequestID dài hơn 20 ký tự, cancel item thiếu hoặc có cả OrderID/ClientRequestID | Sửa input rồi gửi lại. Chưa có request nào được gửi |
*ssisdk.DuplicateRequestError | HTTP 409: ClientRequestID hoặc batchRequestId đã dùng trong ngày | Đừng đặt lại ngay với id mới. Kiểm tra lệnh gốc có còn hiệu lực không |
*ssisdk.RateLimitError | HTTP 429 | Chờ RetryAfterMs (nếu có), rồi gửi lại có kiểm soát. SDK không tự gửi lại request ghi |
*ssisdk.AuthenticationError | Token hết hạn hoặc thiếu quyền (401/403) | Refresh token hoặc xác thực lại bằng OTP |
*ssisdk.APIError | Lỗi server khác, ví dụ 400111 (ErrReplayWindowExceeded) khi batchRequestTime nằm ngoài replay window | Đọc Code, StatusCode, Message. Giữ đồng hồ máy chính xác (NTP) |
Khi timeout hoặc mất kết nối, bạn không thể biết batch đã tới hệ thống hay chưa vì request ghi không được retry. Hãy đối soát trạng thái lệnh trước (ví dụ qua order event của streaming). Nếu buộc phải gửi lại, dùng lại đúng các ClientRequestID cũ để cơ chế chống trùng 409 vẫn có tác dụng.
Success == false ở từng BatchOrderResult không làm lời gọi trả error; đó là lỗi riêng của lệnh đó (ErrorCode / ErrorMessage).
Trading WebSocket Errors
Xem Trading WebSocket.
func reportWSError(op string, err error) {
var wsErr *ssisdk.TradingWSError
var vErr *ssisdk.ValidationError
var connErr *ssisdk.WebSocketError
switch {
case errors.As(err, &wsErr):
// Server từ chối: Status, ErrorCode, ServerMessage.
fmt.Printf("%s: server refused status=%d code=%s: %s\n",
op, wsErr.Status, wsErr.ErrorCode, wsErr.ServerMessage)
case errors.As(err, &vErr):
fmt.Printf("%s: invalid input (nothing was sent): %v\n", op, vErr)
case errors.Is(err, context.DeadlineExceeded), errors.Is(err, context.Canceled):
fmt.Printf("%s: context ended: %v\n", op, err)
case errors.As(err, &connErr):
fmt.Printf("%s: websocket error: %v\n", op, connErr)
default:
fmt.Printf("%s: error: %v\n", op, err)
}
}(cần import context, errors, fmt và alias ssisdk.)
| Error | Khi nào | Cần làm |
|---|---|---|
*ssisdk.TradingWSError (Status, ErrorCode, ServerMessage) | Frame lỗi từ server (status 4xx hoặc "error", ví dụ FCO_ERROR với fco.*), hoặc frame từ chối sau upgrade (401 token, 403 thiếu scope tradingws:*, 429, 500) | Đọc Status và ErrorCode. 401: xác thực lại. 403: kiểm tra scope. 429: rate limit, chờ theo quota (Client().RateLimit()) rồi gửi lại có kiểm soát. 409 do dùng lại clientRequestID: đừng đặt lại với id mới cho tới khi biết lệnh gốc có còn hiệu lực không |
*ssisdk.ValidationError | Input sai (account rỗng hoặc có wildcard, clientRequestID dài hơn 20 ký tự, signature encoding không hợp lệ, params không phải JSON hợp lệ, ...) | Sửa input. Chưa gửi gì đi |
*ssisdk.WebSocketError | Chưa kết nối, gửi thất bại, mất kết nối trước khi có response, timeout chờ response (không retry), URL không phải wss://, thiếu private key cho lệnh thay đổi trạng thái | Sau timeout, lệnh có thể đã được nhận: đối soát qua order event hoặc QueryOrderBook trước khi gửi lại |
context.DeadlineExceeded / context.Canceled | ctx hết hạn hoặc bị hủy | Xử lý như timeout: đừng giả định lệnh chưa được nhận |
WebSocket client không trả *RateLimitError (type này chỉ dành cho REST); rate limit xuất hiện dưới dạng TradingWSError với Status 429. TradingWSError không bao giờ mang params đã ký. Khi chữ ký bị từ chối (401006, 401008, 401013) ở chế độ auto, SDK gửi lại một lần với encoding còn lại; nếu vẫn lỗi, hãy kiểm tra Config.PrivateKey.
Streaming Errors
Lỗi sau khi stream đã kết nối (rớt mạng, token hết hạn) không được trả về từ các hàm subscribe. Hãy theo dõi qua SetOnConnection (State, Err) và LastError(). Khi State == "failed", SDK ngừng reconnect và bạn phải xác thực lại. Xem Connection events.
| Error | Khi nào | Cần làm |
|---|---|---|
*ssisdk.ValidationError | Loại topic không biết hoặc filter sai, danh sách rỗng, interval khác tick / 1m / 5m, interval trên topic không hỗ trợ, interval trên index có wildcard | Sửa input. Chưa gửi gì đi |
*ssisdk.WebSocketError | ListSubscription / UnsubscribeAll timeout khi chờ phản hồi LIST_SUBSCRIPTION; gửi frame thất bại | Kiểm tra IsConnected(), thử lại khi kết nối ổn định |
Lỗi từ Connect() | Xác thực thất bại (sau khi đã refresh token), hoặc server từ chối kết nối | Lỗi xác thực: Authenticate lại với OTP mới. 429: chờ, và kiểm tra số socket đang mở (tối đa 10 mỗi client) |
Hướng dẫn xử lý
- Dùng
errors.As(không dùng type switch) và kiểm tra type cụ thể trước:DuplicateRequestErrortrướcAPIError;ReauthenticationRequiredError/SmartOTPPendingError/SmartOTPRejectedErrortrướcAuthenticationError;TradingWSErrortrướcWebSocketError. - Bắt
*ssisdk.AuthenticationErrorkhi cần làm mới token hoặc lấy OTP mới. - Bắt
*ssisdk.RateLimitErrorvà tuân thủRetryAfterMs(kiểm tranil) để backoff. - Bắt
*ssisdk.APIErrorđể xử lý lỗi nghiệp vụ từ server. - Bắt
*ssisdk.WebSocketErrorkhi xử lý kết nối streaming. - Bắt
*ssisdk.ValidationErrorđể kiểm tra input trước khi gọi API. - Dùng
*ssisdk.SSIErrorlàm fallback cho tất cả lỗi SDK. - Đừng tự log token, chữ ký hay OTP.
TokenvàConfigin giá trị đã che quaString().