Go

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 TypeEmbedMô tả
*ssisdk.SSIError—Error cơ sở. Các field: Message, Code (string), StatusCode (int), ResponseBody, Headers
*ssisdk.AuthenticationErrorSSIErrorXác thực thất bại (sai credentials, sai OTP, token hết hạn)
*ssisdk.SmartOTPPendingErrorAuthenticationErrorSmart OTP chưa được duyệt (HTTP 202 / mã 401114). Poll lại với cùng transaction id
*ssisdk.SmartOTPRejectedErrorAuthenticationErrorSmart OTP bị từ chối, không tìm thấy hoặc thuộc giao dịch khác (401113, 401115, 401116)
*ssisdk.ReauthenticationRequiredErrorAuthenticationErrorPhiên không thể refresh nữa. Cần OTP mới; SDK xóa token đang lưu
*ssisdk.APIErrorSSIErrorAPI trả về lỗi — có thêm StatusCode, ResponseBody
*ssisdk.DuplicateRequestErrorAPIErrorHTTP 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.RateLimitErrorSSIErrorVượt giới hạn request (HTTP 429, code RATE_LIMITED). Các field: RetryAfterMs *int, Limit *int, Remaining *int, Reset *float64
*ssisdk.WebSocketErrorSSIErrorLỗi kết nối hoặc giao tiếp WebSocket
*ssisdk.TradingWSErrorWebSocketErrorServer 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.ValidationErrorSSIErrorLỗ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ómTên và mã
Auth / account / OTPErrCredentialNotLinkedToClient 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
SignatureErrSignatureMissing 401006, ErrSignatureBodyEmpty 401008, ErrSignatureMismatch / ErrSignatureInvalid 401013
TradingErrModifyPriceAndQuantity 400107, ErrReplayWindowExceeded 400111
Market validatorsErrSymbolInvalidCharacters 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):

ErrorKhi nàoCần làm
*ssisdk.ValidationErrorBatch 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/ClientRequestIDSửa input rồi gửi lại. Chưa có request nào được gửi
*ssisdk.DuplicateRequestErrorHTTP 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.RateLimitErrorHTTP 429Chờ 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.AuthenticationErrorToken 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.APIErrorLỗ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.)

ErrorKhi nàoCầ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.ValidationErrorInput 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.WebSocketErrorChư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áiSau 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.Canceledctx hết hạn hoặc bị hủyXử 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.

ErrorKhi nàoCần làm
*ssisdk.ValidationErrorLoạ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ó wildcardSửa input. Chưa gửi gì đi
*ssisdk.WebSocketErrorListSubscription / UnsubscribeAll timeout khi chờ phản hồi LIST_SUBSCRIPTION; gửi frame thất bạiKiể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ốiLỗ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: DuplicateRequestError trước APIError; ReauthenticationRequiredError / SmartOTPPendingError / SmartOTPRejectedError trước AuthenticationError; TradingWSError trước WebSocketError.
  • Bắt *ssisdk.AuthenticationError khi cần làm mới token hoặc lấy OTP mới.
  • Bắt *ssisdk.RateLimitError và tuân thủ RetryAfterMs (kiểm tra nil) để backoff.
  • Bắt *ssisdk.APIError để xử lý lỗi nghiệp vụ từ server.
  • Bắt *ssisdk.WebSocketError khi xử lý kết nối streaming.
  • Bắt *ssisdk.ValidationError để kiểm tra input trước khi gọi API.
  • Dùng *ssisdk.SSIError làm fallback cho tất cả lỗi SDK.
  • Đừng tự log token, chữ ký hay OTP. Token và Config in giá trị đã che qua String().

Trên trang này