Zum Inhalt springen
← Zurück zum Blog

Ich brauchte ein Go-SDK für Wise. Niemand hat eins gebaut.

Veröffentlicht:
Themen:gotype-designapi-clients

2020 baute ich einen Discord-Bot, der meine internationalen Mitarbeiter über Wise bezahlt. Kotlin, direkte REST-Calls, v1- und v3-Endpoints. Quotes, Transfers, Funding — alles über Discord-Commands. Es hat funktioniert. Es war fine. Ich bin weitergezogen.

Jahre später baue ich meine eigene Accounting-Software. Ich betreibe zwei deutsche GmbHs über mehrere Länder, ich hasse jedes Accounting-Produkt, das ich ausprobiert habe, und ich brauche Echtzeit-Banking-Daten. Also startete ich bank-sync — ein Go-CLI, das Transaktionen von Wise und Qonto in eine lokale SQLite-Datenbank zieht, die ich kontrolliere.

Ich brauchte ein Go-SDK für Wise. Sicherlich hatte bis 2026 jemand eins gebaut. Es gibt eine API-Reference-Page. Es gibt SDKs für andere Sprachen. Es gibt sogar eine OpenAPI-Spec zum Download.

Es gibt kein offizielles Go-SDK. Die Community-SDKs sind dünne Wrapper um net/http, die Wise’s Wire-Typen direkt exponieren — float64 für Geld, string für Datumsangaben, ungetypte IDs.

Also baute ich wise-go.

float64-Geld in einer Finanz-API

Wise ist nicht mal konsistent damit. Balance-Statement-Werte kommen als 9.94 zurück. Transfer-Webhooks senden amount: 120. Das Balance-Statement-Endpoint liefert Major Units als Floats. Der Webhook sendet einen Integer. Gleiche API, gleiche Währung, andere Repräsentation. Du kannst keinem Typ einer API vertrauen, die sich selbst nicht vertraut.

In wise-go kommt Geld als float64 in einem unexportierten internal/raw-Package rein und geht als int64 Cents raus. Die Konvertierung passiert in einer Funktion:

func (a BalanceAmount) Cents() int64 {
    return int64(math.Round(a.Value * centsPerUnit))
}

Wenn Wise das Wire-Format ändert — und das wird passieren — ist der Blast-Radius diese Funktion, nicht jeder Call-Site in jeder Consumer-Codebase.

Was ich bewusst nicht gebaut habe

Einen Money-Typ, der Arithmetik kann. Jeder Go-Entwickler, der type Money struct { Cents int64; Currency Currency } sieht, greift nach func (m Money) Add(other Money) Money. Das habe ich nicht geschrieben. In dem Moment, in dem du Add hinzufügst, musst du entscheiden: was passiert, wenn Währungen nicht übereinstimmen? Konvertierst du? Zu welchem Kurs? Wessen Kurs? Jetzt bist du eine Financial Library, kein API-Client.

Ich habe auch keine Pagination gebaut. Wise paginiert ihren Transaktions-Endpoint nicht. Spekulativ einen Page[T]-Typ für eine API zu bauen, die nicht paginiert, ist, wie man mit Abstraktionen endet, die Annahmen encodieren, die die API nicht trifft.

Der CARD_REFUND-Bug

In v0.3.0 fand ich einen Bug in meinem eigenen Transaktions-Typ-Klassifizierer. Wise hat einen Transaktionstyp namens CARD_PAYMENT. Wenn der Betrag positiv ist — eine Rückerstattung statt einer Belastung — sollte er als CARD_REFUND klassifiziert werden. Mein Code klassifizierte positive-amount CARD_PAYMENT-Transaktionen als Card Payments. Still, falsch, und exakt die Art Bug, die ungetypte Enums einfach machen und getypte Enums unmöglich.

Der Fix war fünf Zeilen. Aber es ist die Art Bug, die deine Typgrenze verhindern soll: wenn die Typen der API unzuverlässig sind, ist deine Grenze das Einzige zwischen einer stillen Fehlklassifizierung und einer korrekten.

Was ich anders machen würde

Ich habe viel mit AI-Coding-Agents gearbeitet, und das Projekt ging schnell voran. Aber ich hätte die Agenten aggressiver auf Wise’s Dokumentation zeigen und früher auf strikte Typen bestehen sollen. Die float64-zu-int64-Konvertierung und die Branded IDs hätten ab dem ersten Commit da sein sollen, nicht in späteren Versionen. Ich hätte auch früher mehr Sandbox-Testing machen sollen — den CARD_REFUND-Bug im Sandbox statt im Code-Review finden.

Wo es steht

wise-go ist bei v0.5.0. Drei Ressourcen — Profiles, Balances, Transactions — alle read-only. Keine Transfers. Keine Webhooks. 94.8% Test-Coverage. Eine flache Package-Struktur, von der ich weiß, dass sie nicht über sechs weitere Ressourcen skalieren wird.

Die Typgrenze ist der Vertrag. Alles andere — die Dokumentation der API, ihr Wire-Format, ihre Enum-Varianten — ist, wogegen du dich verteidigst.

Lies den Code. Er ist besser als Wise’s Dokumentation.