Módulogithub.com/Appiasoft/compense-saas-sdk-go (paquete compense, subpaquete webhook)
Versión1.1.0 (compense.Version) · API v1
RequisitosGo 1.21+ (iteradores range-over-func con Go 1.23+)
DependenciasSolo la biblioteca estándar
RepositorioAppiasoft/compense-saas-sdk-go · guías en docs/ · ejemplos en examples/

Instalar #

Shell
go get github.com/Appiasoft/compense-saas-sdk-go@v1.1.0
# Repositorio privado: GOPRIVATE=github.com/Appiasoft/* y credenciales de git

Cliente #

Go
import compense "github.com/Appiasoft/compense-saas-sdk-go"

client, err := compense.NewClient(os.Getenv("COMPENSE_API_KEY"),
	compense.WithEnvironment(compense.Sandbox),  // Production (defecto) | Sandbox | Dev
	compense.WithBaseURL("https://gw.example.co"), // otra URL; gana sobre el entorno
	compense.WithHTTPClient(myHTTPClient),       // proxies, mTLS, tracing, pruebas (no se modifica)
	compense.WithTimeout(30*time.Second),        // por intento (solo cliente creado por el SDK)
	compense.WithRetry(compense.RetryConfig{MaxAttempts: 3}),
	compense.WithRetryWrites(false),
	compense.WithUserAgent("horizonte-erp/2.0"),
	compense.WithLogger(slog.Default()),         // avisos de deprecación
)
if err != nil {
	log.Fatal(err) // *compense.ConfigError
}

El *Client es seguro para uso concurrente: crea uno por llave y reutilízalo. Cada método recibe un context.Context primero (plazos y cancelación, también durante las esperas entre reintentos).

Convenciones #

  • Servicios en PascalCase con los mismos espacios de nombres que Python: client.Wallet.Bills.Pay, client.Admin.HR.CreateNovelty, client.PQRS.Submit.
  • Parámetros opcionales en structs *Params (pasa nil si no tienes); los campos vacíos no se envían. Booleanos opcionales con compense.Bool(true).
  • La Idempotency-Key va como opción variádica: compense.WithIdempotencyKey("...").
  • Escrituras → *compense.Transaction (Data map[string]any); lecturas → modelos (*Balance, *Resolution, *Page, *OffsetPage…) o map[string]any; todo modelo guarda Raw.
  • Las respuestas se decodifican con json.Number: los números conservan sus dígitos exactos.
  • Montos: compense.Amount acepta constantes de texto ("65000") o AmountFromInt, AmountFromRat y AmountFromFloat (desaconsejado).
  • Decisiones: compense.DecisionApprove / compense.DecisionReject.

Errores #

Toda respuesta no 2xx es un *compense.APIError (StatusCode, Title, Detail, TraceID, Errors(), RetryAfter, Problem). Clasifícalos con errors.Is o los helpers:

EstadoCentinelaHelper
401, 403ErrAuthIsAuthError
404ErrNotFoundIsNotFound
409ErrIdempotencyConflictIsIdempotencyConflict
422ErrValidationIsValidationError
429ErrRateLimitedIsRateLimited
502, 504ErrUpstreamIsUpstreamError
otros 5xxErrServerIsServerError

El cliente del SDK no sigue redirecciones: un 3xx llega como *APIError.

Paginación #

Go
it := compense.NewPageIterator(ctx, func(ctx context.Context, page int) (*compense.Page, error) {
	return client.Webhooks.Deliveries.List(ctx, &compense.DeliveryListParams{Status: "failed", Page: page, PageSize: 100})
})
for it.Next() {
	client.Webhooks.Deliveries.Replay(ctx, fmt.Sprint(it.Item()["id"]))
}
if err := it.Err(); err != nil {
	log.Fatal(err)
}
// Go 1.23+: for item, err := range it.All() { ... }

NewOffsetIterator hace lo mismo con las listas limit/offset.

Webhooks #

Go
import "github.com/Appiasoft/compense-saas-sdk-go/webhook"

http.Handle("/compense/webhooks", webhook.Handler([]string{newSecret, oldSecret},
	func(ctx context.Context, ev *webhook.Event) error {
		if ev.IsTest() || alreadyApplied(ev.ID) {
			return nil
		}
		return handle(ev.Event(), ev.Resource())
	}))

Handler responde 401 si la verificación falla, 500 si tu función devuelve error (para que Compense reintente) y 200 en otro caso. Fuera de net/http usa webhook.Verify(rawBody, header, secrets, opts...) o webhook.VerifyRequest(r, secrets). Opciones: WithTolerance, WithoutTolerance, WithNow. Errores: webhook.ErrVerification (ErrSignature, ErrTimestamp) y ErrNoSecret. Ver Verificar la firma.

Pruebas #

Levanta un httptest.Server y apunta el cliente a él:

Go
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
	if r.Header.Get("X-API-Key") != "cmp_test" {
		w.WriteHeader(http.StatusUnauthorized)
		return
	}
	w.Header().Set("Content-Type", "application/json")
	fmt.Fprint(w, `{"total":"150000.00","available_cash":"150000.00","currency":"COP"}`)
}))
defer srv.Close()

client, _ := compense.NewClient("cmp_test", compense.WithBaseURL(srv.URL))
balance, _ := client.Wallet.Balance(context.Background(), "1032456789")
fmt.Println(balance.Available.Amount) // 150000.00