Go SDK
0.1.1 is publicly available from its source tag and the official Go module proxy. A fresh isolated download verified the archive against sum.golang.org; the public checksum record (opens in a new tab) contains both module and go.mod hashes.
The 0.1.1 release adds a concrete merchant-site presentation example. Embedded checkout is the default: load only the official Lunixi bundle and mount the existing form; methods and multi-card choices stay in the Lunixi panel. Optional hosted mode uses the backend paymentPageUrl after exact HTTPS pay.lunixi.com/payment/ validation. Its token may differ from the embedded token. Missing URLs keep embedded available and hosted on hold/reconciliation with the original attempt/key. Never derive a URL from the browser token. Authorize the customer/order and native browser CSRF before rendering, send Cache-Control: private, no-store, preserve the merchant CSP nonce and keep tokens/HTML/redirect URLs out of logs. Generic response data preserves paymentPageUrl; no new typed product parity is claimed.
github.com/Lunixi-Payment/lunixi-go is a server-side client with no runtime dependencies. It works with net/http, Gin, Echo, Fiber and other Go frameworks. Go 1.26 or newer is required.
Installation
Add the requirement to your application's existing go.mod. Keep its existing module declaration and Go directive; the SDK requires Go 1.26 or newer.
require github.com/Lunixi-Payment/lunixi-go v0.1.1After adding the SDK import shown below, download the required version and reconcile your application's dependencies:
go mod download github.com/Lunixi-Payment/lunixi-go
go mod tidySource and examples (opens in a new tab) · Go reference (opens in a new tab)
Configure once per merchant
import (
"os"
"time"
lunixi "github.com/Lunixi-Payment/lunixi-go"
)
client, err := lunixi.New(lunixi.Config{
BaseURL: "https://api-gateway.lunixi.com",
KeyID: os.Getenv("LUNIXI_KEY_ID"),
PrivateKey: os.Getenv("LUNIXI_PRIVATE_KEY_PEM"),
Timeout: 30 * time.Second,
MaxRetries: 2,
})
if err != nil { return err }Register your Ed25519 public key in the dashboard and keep the private key on your server. PKCS8 PEM, base64 PKCS8 DER, base64 32-byte seed and a validated base64 64-byte Ed25519 private key are accepted. The merchant key selects TEST or LIVE. Never put a private key or webhook secret in a browser bundle.
Share one client between goroutines. It signs requests, caches access tokens and coalesces concurrent refresh. HTTPS is required and redirects are disabled. BaseURL must be a trusted origin with no path, credentials or query. Do not configure an insecure TLS transport.
| Configuration | Default | Limit |
|---|---|---|
Timeout | 30 seconds per request | At most 2 minutes; also use a context deadline |
MaxRetries | 0 | 0–5, GET or a stable idempotency key only |
MaxResponseBytes | 8 MiB | At most 32 MiB |
HTTPClient | Standard client | Copied; redirect following is disabled |
AllowInsecureLocalhost | false | Explicit local tests only, literal loopback or localhost |
Create checkout
Read order totals from your server-side records. Persist a stable attempt key before calling the gateway, then reuse it with the same request if the outcome is unknown.
ctx, cancel := context.WithTimeout(context.Background(), 35*time.Second)
defer cancel()
response, err := client.Payments.CreateIntent(ctx, lunixi.IntentRequest{
Amount: 10050,
Currency: "TRY",
OrderID: "ORD-1001",
CallbackURL: "https://merchant.example/payment/return",
BasketItems: []lunixi.BasketItem{
{ID:"line-1", Name:"Example product", ItemType:"PHYSICAL", Price:10050},
},
}, persistedAttemptKey)
if err != nil { return err }
var checkout struct { Token string `json:"token"` }
if err := response.DecodeData(&checkout); err != nil { return err }
if checkout.Token == "" { return errors.New("missing checkout token") }The sample uses context and errors from the standard library. persistedAttemptKey is the key saved with your payment attempt. Supply the returned token only to the authenticated customer for that order, with Cache-Control: no-store, and initialize the existing Lunixi checkout form. Do not log the token or put it into analytics.
Money is positive integer minor units: 10050 means 100.50 TRY. Basket Price is the line total; the sum must equal Amount. Unsafe JSON integer values are rejected. Use amount as the root payment total; installments and their fees are selected in the Lunixi form. Optional buyer/address and documented insurance basket details are available on IntentRequest. Method restrictions can only narrow the methods enabled in your Lunixi panel.
The form handles enabled card and alternative methods. The Go SDK does not collect raw cards. A token, browser return, HTTP success or AUTHORIZED status is not proof of capture. Finalize orders only after authenticating the gateway result and matching merchant, order, payment ID, expected amount/currency and final capture/reconciliation state. Handle refunds, voids and disputes as separate transitions.
Supported APIs
| Client | Helpers |
|---|---|
Payments | CreateIntent, Get, List, Methods, Capture, Refund, Void |
Payments split | Split, GetSplit, RefundSplit |
Fraud | Evaluate, RecordEvent, Decisions, Decision |
Client.Do | Other documented merchant /api/v1/ endpoints with the same signed transport |
WebhookVerifier / SQLInbox | Raw v2 verification and durable replay processing |
Version 0.1.1 has no separate typed clients for wallet, bank-connect, payment links, insurance, marketplace, subscription or identity. Use their documented REST schemas with Do; this is not a claim of typed feature parity.
partial := int64(2500)
response, err := client.Payments.Refund(ctx, paymentID, &partial, "Customer return", persistedRefundKey)nil amount requests a full capture/refund. Void releases an authorization. Split validates integer leg amounts, 1–5 legs and the total, while method-specific schema validation remains in the gateway. Pending split states may return a response with HTTP 400 and must remain unpaid. Check status and aggregate balance before fulfillment.
Responses preserve Envelope, Data, PageInfo and binary Bytes. DecodeData uses json.Number for interface values. APIError exposes safe StatusCode, Code and RequestID, while its error string omits upstream bodies/messages. Returned objects may contain customer data; do not log them.
Financial helpers require an idempotency key of 16–128 non-space printable ASCII characters. Configured retries keep the same key/body and use fresh signature nonces. Unkeyed mutations are never automatically retried. Reconcile an uncertain result before creating another payment.
Webhooks with durable deduplication
Verify raw bytes before JSON middleware. The v2 HMAC binds event ID, exact timestamp and raw body; comparison is constant-time. The default freshness tolerance is five minutes, configurable up to fifteen minutes. It cannot be disabled. The signed JSON body must contain type; an unsigned type header alone cannot authorize an action. Duplicate headers/JSON keys and ID/type mismatches fail closed.
verifier, err := lunixi.NewWebhookVerifier(lunixi.WebhookConfig{
Secrets: []string{os.Getenv("LUNIXI_WEBHOOK_SECRET")},
})
if err != nil { return err }
event, err := verifier.VerifyRequest(ctx, request)
if err != nil { return err }
inbox, err := lunixi.NewSQLInbox(db, "lunixi_webhook_inbox", merchantScope)
if err != nil { return err }
// Run EnsureSchema during startup/migrations, once.
applied, err := inbox.Process(ctx, event, func(tx *sql.Tx) error {
return applyVerifiedOrderTransition(ctx, tx, event)
})db is your database/sql connection; applyVerifiedOrderTransition is your application adapter that validates the financial fields and uses the supplied transaction. The inbox supports PostgreSQL and SQLite with an application-supplied driver. Use separate scopes per merchant. The event marker and order transition must commit in the same transaction. Duplicate delivery skips the callback, payload collisions fail closed, and callback failure rolls back the marker so a provider retry remains safe.
Use an outbox for external effects. Retain deduplication markers for your delivery/replay policy. Up to three active/previous secrets support rotation. Request a fresh provider replay for old events rather than removing timestamp checks.
The runnable SQLite webhook example (opens in a new tab) records verified notification IDs/types transactionally and includes rollback, restart, concurrent duplicate and HTTP tamper tests. It does not fulfill orders. Add your validated financial adapter in the transaction callback. The example requires CGO and a C compiler; the SDK itself does not. Bind the example behind a trusted HTTPS reverse proxy.
Verify your integration
go test -race ./...
go vet ./...
go build ./...
go run golang.org/x/vuln/cmd/[email protected] ./...Run the same checks in examples/webhook. CI verifies Go 1.26/1.27, independent Node.js canonical JSON/Ed25519 vectors, raw v2 HMAC fixtures and real TLS test servers. All fixture credentials are synthetic; no live payment calls are needed. See security policy (opens in a new tab).
Merchant-page integration example
The source example defines RenderCheckoutHTML(token, nonce) and HostedCheckoutURL(paymentPageUrl) in presentation.go (opens in a new tab). Copy/adapt these helpers inside your existing authorized, CSRF-protected merchant controller. The CLI defaults to LUNIXI_CHECKOUT_MODE=embedded; hosted validates the backend URL. It renders/validates internally and intentionally prints neither HTML, token nor URL. These example functions are not new SDK client methods.