Zum Hauptinhalt springen
tsecurity.de LIVE
Echtzeit-Radar & Feeds
Alle RSS Feeds
👥 Community & Social
Sichere ProgrammierungMCP Tool Poisoning: A Name Allowlist Is Not Enough(22.09.2026 um 18:56 Uhr)
Sichere ProgrammierungGiggleGigs: A Job Board That Actually Uses AI Where It Helps(22.09.2026 um 18:57 Uhr)
Sichere ProgrammierungThe Bootstrapped SaaS Flywheel: How to Hit $10k MRR Without VC Capital(22.09.2026 um 19:05 Uhr)
Sichere ProgrammierungThe container was running software nobody built(22.09.2026 um 19:08 Uhr)
Sichere ProgrammierungYour Terminal Tasks, Everywhere: Introducing Tasku Cloud(22.09.2026 um 19:08 Uhr)
Sichere ProgrammierungMCP Tool Poisoning: A Name Allowlist Is Not Enough(22.09.2026 um 18:56 Uhr)
Sichere ProgrammierungGiggleGigs: A Job Board That Actually Uses AI Where It Helps(22.09.2026 um 18:57 Uhr)
Sichere ProgrammierungThe Bootstrapped SaaS Flywheel: How to Hit $10k MRR Without VC Capital(22.09.2026 um 19:05 Uhr)
Sichere ProgrammierungThe container was running software nobody built(22.09.2026 um 19:08 Uhr)
Sichere ProgrammierungYour Terminal Tasks, Everywhere: Introducing Tasku Cloud(22.09.2026 um 19:08 Uhr)
Intelligence View
⚡ tsecurity.de Intelligence

I Built an Open Source Go SDK for Squad by GTCO. Here Is What I Learned About Payment API Design.

TL;DR I built go-squad — a comprehensive, idiomatic Go SDK for the Squad by GTCO payment gateway Zero external runtime dependencies, full test coverage, and a squadtest mock server for unit testing integrations without hitting real A…

0
↗ Quelle (dev.to)
Reagiere als Erste:r — dein Feedback zählt!




TL;DR




  • I built go-squad — a comprehensive, idiomatic Go SDK for the Squad by GTCO payment gateway

  • Zero external runtime dependencies, full test coverage, and a squadtest mock server for unit testing integrations without hitting real APIs

  • This post covers the design decisions behind the SDK and what building it taught me about payment API design in the Nigerian fintech context









go get github.com/kingztech2019/go-squad






Docs: pkg.go.dev/github.com/kingztech2019/go-squad

Repo: github.com/kingztech2019/go-squad







Why I built this



I have been building fintech products in Nigeria for a while. Squad by GTCO is one of the most capable payment gateways in the market — it handles transactions, virtual accounts, transfers, VAS, sub-merchant management, disputes, and webhooks. The API surface is genuinely broad.



But every time I integrated it into a Go project, I was writing the same boilerplate. HTTP clients. JSON marshalling. Pagination loops. Webhook signature validation. Error handling for the specific error shapes Squad returns. And every project had slightly different, slightly wrong implementations of the same things.



The most common bug I kept seeing — in my own code and in code I reviewed — was the kobo confusion.



The Squad API uses kobo as the base denomination for NGN amounts. ₦5,000 is 500000 in the API. If you pass 5000 instead, you charge the customer ₦50, and nobody notices until reconciliation. It is the kind of bug that slips through tests because the payment succeeds, just for the wrong amount.



I wanted a single, well-tested library that handled all of this correctly, consistently, and idiomatically in Go. So I built one.







What the SDK covers





client := squad.New(os.Getenv("SQUAD_SECRET_KEY"))

// Payments
resp, err := client.Transactions.InitiatePayment(ctx, &squad.InitiatePaymentParams{
Email: "[email protected]",
Amount: squad.NGN(5000), // ₦5,000 — no kobo confusion
Currency: "NGN",
CallbackURL: "https://yoursite.com/callback",
})

// Virtual accounts
account, err := client.VirtualAccounts.Create(ctx, &squad.CreateVirtualAccountParams{
CustomerIdentifier: "cust-001",
FirstName: "Adaeze",
LastName: "Okafor",
MobileNum: "2348012345678",
Email: "[email protected]",
BVN: "12345678901",
DOB: "01/01/1990",
})

// Transfers
transfer, err := client.Transfers.FundsTransfer(ctx, &squad.FundsTransferParams{
TransactionRef: "pay-out-001",
Amount: squad.NGN(2000),
BankCode: "057",
AccountNumber: "0123456789",
AccountName: "John Doe",
Currency: "NGN",
})

// VAS — airtime, data, cable TV, electricity, SMS
_, err = client.VAS.BuyAirtime(ctx, &squad.BuyAirtimeParams{
PhoneNumber: "2348012345678",
Amount: squad.NGN(50),
Network: "MTN",
TransactionRef: "air-001",
})





The full surface covers transactions, virtual accounts, transfers, sub-merchant management, disputes with evidence upload, all VAS operations, and webhooks.







Design decisions worth talking about





1. Money helpers that prevent the most common bug





squad.NGN(5000)       // → 500000 (₦5,000 in kobo)
squad.NGN(1) // → 100 (₦1.00 in kobo)
squad.USD(50) // → 5000 ($50.00 in cents)
squad.FromKobo(50000) // → 500.0 (display value)
squad.FromCents(5000) // → 50.0 (display value)





This is the first thing I added. Every amount field in the SDK is int64 (kobo), not float64 (naira). You cannot accidentally pass a naira amount — the type system pushes you toward the helpers. If you call squad.NGN(5000), you get 500000. There is no ambiguity.



This sounds obvious. But I have seen this bug in production codebases multiple times. The fix is architectural — make the wrong thing hard to write.





2. Auto-pagination iterator



Every listing endpoint in the Squad API is paginated. Most codebases I have seen implement the same manual loop:




// Without iterator — repetitive and error-prone
page := 1
for {
result, _ := client.Transfers.GetAllTransactions(ctx, &squad.TransferListParams{Page: page, PerPage: 50})
for _, t := range result.Transfers { process(t) }
if len(result.Transfers) < 50 { break }
page++
}






The SDK exposes an Iter type that fetches pages lazily:




// With iterator — clean, handles all edge cases automatically
iter := client.Transfers.All(ctx, &squad.TransferListParams{PerPage: 50})
for iter.Next() {
process(iter.Item())
}
if err := iter.Err(); err != nil { log.Fatal(err) }






The iterator is generic — Iter[T] — so it works across all listing endpoints with full type safety. Page fetching happens lazily on each Next() call, so you only pay for the pages you actually consume.



This pattern is borrowed from the Stripe Go SDK, which I think is the gold standard for payment SDK design.






3. Idempotency keys to prevent duplicate charges



Network failures during payment requests are a real problem in Nigeria. If you retry a payment initiation request after a timeout, you risk charging the customer twice.



The SDK supports idempotency keys at the context level:




key, err := squad.GenerateIdempotencyKey()
// Store key in your DB alongside the order BEFORE calling Squad.
ctx = squad.WithIdempotencyKey(ctx, "order-"+orderID+"-"+key)
resp, err := client.Transactions.InitiatePayment(ctx, params)
// On network failure, retry with the SAME ctx — same key, no double charge.






Or you can opt into automatic key generation for all POST requests:




client := squad.New(key, squad.WithAutoIdempotency())






The important design decision here is that idempotency is attached to the context, not the params struct. This means you can inject it from your middleware layer without touching your business logic.






4. The webhook router



Webhook handling in most codebases is a giant switch statement on event type strings, with manual signature validation scattered across the handler. The SDK ships a typed webhook router:




router := squad.NewWebhookRouter(os.Getenv("SQUAD_SECRET_KEY")).
OnTransactionSuccess(func(ctx context.Context, body *squad.WebhookTransactionBody) error {
return fulfillOrder(body.TransactionRef, body.Amount)
}).
OnVirtualAccountCredit(func(ctx context.Context, body *squad.WebhookVirtualAccountBody) error {
return creditCustomer(body.CustomerIdentifier, body.Amount)
}).
OnDisputeOpened(func(ctx context.Context, body *squad.WebhookDisputeBody) error {
return notifyTeam(body.TicketID, body.Reason)
}).
OnError(func(w http.ResponseWriter, r *http.Request, err error) {
log.Printf("webhook error: %v", err)
http.Error(w, "error", http.StatusInternalServerError)
})

http.Handle("/webhook/squad", router)






The router validates the HMAC-SHA512 signature, parses the body into the correct typed struct based on the event type, and dispatches to your handler. It implements http.Handler directly so you can register it with any standard Go HTTP framework.



Signature validation uses constant-time comparison to prevent timing attacks.






5. The squadtest mock server



This is the part I am most pleased with.



Testing payment integrations is painful. You either hit a real sandbox (slow, flaky, requires network) or you mock at the HTTP level (verbose, not type-safe). The squadtest package provides a full mock Squad API server:




func TestMyCheckoutService(t *testing.T) {
srv := squadtest.NewServer(t) // starts a mock server, shuts down when test ends

srv.OnInitiatePayment(func(p *squad.InitiatePaymentParams) (*squad.InitiatePaymentResponse, error) {
if p.Amount != squad.NGN(5000) {
t.Errorf("unexpected amount: %d", p.Amount)
}
return &squad.InitiatePaymentResponse{
CheckoutURL: "https://fake-checkout.squadco.com/abc",
TransactionRef: p.TransactionRef,
}, nil
})

// Inject srv.Client() into your service under test
myService := checkout.NewService(srv.Client())

url, err := myService.StartCheckout("[email protected]", squad.NGN(5000))
if err != nil { t.Fatal(err) }

// Assert on what your code actually sent
if srv.RequestCount() != 1 {
t.Errorf("expected 1 request, got %d", srv.RequestCount())
}
}






The mock server:




  • Starts and stops automatically with the test lifecycle via t.Cleanup

  • Returns typed responses that match the real API shape

  • Records all requests so you can assert on what your code sent

  • Supports custom handlers for any endpoint via srv.Handle(method, path, fn)



No real network calls. No sandbox credentials needed in CI. Fast and deterministic.




go get github.com/kingztech2019/go-squad/squadtest












What building this taught me



Nigerian payment APIs have genuinely different constraints from Western ones.



The kobo denomination issue is the obvious one. But there are subtler things. The NUBAN virtual account flow requires BVN, and building that into a type-safe API surface means thinking carefully about which fields are always required versus sometimes required. The VAS surface — airtime, data, cable TV, electricity — reflects a market where bills are paid through payment APIs rather than through bank direct debits. That changes the shape of the API significantly.



Zero external runtime dependencies is worth the effort.



The SDK has no runtime dependencies beyond the Go standard library. Every dependency you add is a dependency your users have to manage, audit, and update. For a payments library — where supply chain security matters — keeping the dependency surface minimal is not just aesthetics. It is a security posture.



The test infrastructure is part of the product.



The squadtest package is not a nice-to-have. It is one of the most important parts of the library. If developers cannot test their integration easily, they will not write tests. If they do not write tests, they will have production bugs. A payment SDK that makes testing hard is a payment SDK that ships broken integrations into production.









Using it in your project






go get github.com/kingztech2019/go-squad






Requires Go 1.21 or later.



Sandbox is auto-detected from your key prefix — if your key starts with sandbox_sk_, the SDK routes to the sandbox API automatically. No configuration needed.




import (
"context"
"os"

squad "github.com/kingztech2019/go-squad"
)

client := squad.New(os.Getenv("SQUAD_SECRET_KEY"))






Full documentation on pkg.go.dev: pkg.go.dev/github.com/kingztech2019/go-squad









Contributing



Contributions are welcome. The areas I am most interested in help with:




  • More squadtest handler coverage for endpoints not yet mocked

  • Additional examples in the examples/ directory

  • Documentation improvements



See CONTRIBUTING.md for guidelines.









Discussion



A few questions I am genuinely curious about from the Go community:



If you have integrated payment gateways in Go before — what is the most common mistake you have seen in payment integration code? And is there a pattern in the SDK you would have designed differently?



Drop your thoughts in the comments. I read everything.

Ähnliche Beiträge
🔍 Verwandte News

Auch interessante Nachrichten I Built an Open Source Go SDK for Squad by GTCO. Here Is What I Learned About Payment API Design.

Thematisch verwandte Begriffe: Built, Open, Source, Squad · 6 Treffer

Laden...

Beiträge werden geladen ...

Laden...

Videos werden geladen ...

Laden...

Beiträge werden geladen ...

Laden...

Videos werden geladen ...

Laden...

Beiträge werden geladen ...

Laden...

Videos werden geladen ...

Laden...

Beiträge werden geladen ...

Laden...

Videos werden geladen ...

Zum Aktualisieren ziehen
ZERO-DAY CVE-2026-75517 | Novu provides an API for sending notifications through multiple channels…
Advisory →
TTS Reader • tsecurity.de Voice
tsecurity.de Icon
tsecurity.de App
Offline-Lesen, Eilmeldungen & 0ms Ladezeit

Installiere tsecurity.de direkt auf deinen Home-Bildschirm für das ultimative Vollbild-Magazinerlebnis ohne Browser-Leisten.

Nächster Beitrag
Themen-Radar & Intelligence Matrix
Echtzeit-Taxonomie nach Angriffsvektoren & Plattformen

tsecurity.de Live Threat Radar

🔴 LIVE RADAR
MONITORING
AKTIV
CVE-DATENBANK
LIVE
🔍
Community Radar & Live Chat
Sentinel Bot online • Live-Stream
Dein Cluster: Security Explorer
Match:
lädt…
Verbindung zum Community-Stream wird aufgebaut...
Bearbeitungsmodus — Senden überschreibt deine Nachricht
Community-Puls — was gerade passiert
lädt…
Aktivitäten deiner Analysten
lädt…
Neues Thema oder Eilmeldung einreichen

Reiche interessante Links, Zero-Days oder Debatten ein. Die Community entscheidet per Upvote über die Veröffentlichung.

Heiß diskutierte Einreichungen
🔖 Gespeicherte Artikel
📂 Keine gespeicherten Artikel vorhanden.
Zurück Ziehen Vor
Links: vorheriger Artikel Rechts: nächster Artikel unten: schließen
News NIS-2 Frühwarnung Tier-1 Intel ⏱️ 3 Min vor 10 Min
Artikeldaten werden geladen...

Zurück: vorheriger Vor: nächster
↗ Original-Quelle
Social Reaktionen Deine Reaktion zählt
Einstufung & Relevanz-Poll 0 Stimmen
In sozialen Netzwerken teilen 1-Klick