Note: This is an unofficial Go SDK for Pakasir. It is not affiliated with, endorsed by, or officially supported by Pakasir. This SDK is unofficial because the official API only provides documentation and support for their REST API and Node.js SDK. This library was created to add proper Go support, and it is actively used by the owner of this repository.
An idiomatic Go SDK for the Pakasir payment gateway. Built with Functional Options, Service-Oriented Architecture, and full i18n support (English & Indonesian).
go get github.com/H0llyW00dzZ/pakasir-go-sdkRequires Go 1.26 or later.
package main
import (
"context"
"fmt"
"log"
"github.com/H0llyW00dzZ/pakasir-go-sdk/src/client"
"github.com/H0llyW00dzZ/pakasir-go-sdk/src/constants"
"github.com/H0llyW00dzZ/pakasir-go-sdk/src/transaction"
)
func main() {
// Initialize client
c := client.New("your-project-slug", "your-api-key")
// Create a QRIS transaction
txnService := transaction.NewService(c)
resp, err := txnService.Create(context.Background(), constants.MethodQRIS, &transaction.CreateRequest{
OrderID: "INV123456",
Amount: 99000,
})
if err != nil {
log.Fatal(err)
}
fmt.Printf("Payment Number: %s\n", resp.Payment.PaymentNumber)
fmt.Printf("Total Payment: %d\n", resp.Payment.TotalPayment)
}- Functional Options — Clean, extensible client configuration
- Service-Oriented — Separate
transaction,simulation, andwebhookservices - Context-First — All I/O operations accept
context.Context - Typed Requests/Responses — No raw maps; fully typed structs with JSON tags
- Buffer Pooling — Memory-efficient request serialization via
bytebufferpool - Exponential Backoff with Jitter — Automatic retry for transient failures (429, 502/503/504, network errors) with Retry-After header support
- i18n — Localized error messages in English and Indonesian
- Sentinel Errors — Programmatic error handling via
errors.Isanderrors.As - Time Parsing Helpers — Unified
ParseTime()on response types - URL Builder — Helper for redirect-based payment integrations
- QR Code Generation — Render QRIS payment strings as PNG images with configurable size, recovery level, and colors
- gRPC Services — Server-side gRPC implementations for transaction and simulation services, with generated client stubs
pakasir-go-sdk/
├── src/
│ ├── client/ # Core HTTP client, configuration, buffer pool
│ ├── constants/ # Payment methods, typed transaction statuses
│ ├── errors/ # Sentinel errors, APIError type
│ ├── i18n/ # Internationalization (EN, ID)
│ ├── transaction/ # Transaction service (create, cancel, detail)
│ ├── simulation/ # Payment simulation service (sandbox)
│ ├── webhook/ # Webhook parsing helper
│ ├── grpc/
│ │ ├── pakasir/v1/ # Generated protobuf code (server + client stubs)
│ │ ├── transaction/ # gRPC TransactionService server
│ │ ├── simulation/ # gRPC SimulationService server
│ │ └── internal/ # Shared enum conversion and test helpers
│ ├── helper/
│ │ ├── gc/ # Buffer pool management
│ │ ├── qr/ # QR code generation for QRIS payments
│ │ └── url/ # Payment URL builder
│ └── internal/
│ ├── request/ # Shared request body and validation
│ └── timefmt/ # Shared RFC3339 time-parsing helper
├── Makefile # Build, test, proto generation, quality analysis targets
├── buf.yaml # Buf module config for proto linting
├── buf.gen.yaml # Buf code generation config
├── proto/ # Protobuf definitions (.proto files)
├── examples/ # Usage examples
├── LICENSE # Apache License 2.0
└── README.md
| API Endpoint | SDK Method | Description |
|---|---|---|
POST /api/transactioncreate/{method} |
transaction.Service.Create() |
Create a new transaction |
POST /api/transactioncancel |
transaction.Service.Cancel() |
Cancel a transaction |
GET /api/transactiondetail |
transaction.Service.Detail() |
Get transaction details |
POST /api/paymentsimulation |
simulation.Service.Pay() |
Simulate payment (sandbox) |
| Webhook POST | webhook.Parse() |
Parse webhook notification |
| Payment URL | url.Build() |
Build payment redirect URL |
| Constant | Value |
|---|---|
constants.MethodQRIS |
qris |
constants.MethodBNIVA |
bni_va |
constants.MethodBRIVA |
bri_va |
constants.MethodCIMBNiagaVA |
cimb_niaga_va |
constants.MethodPermataVA |
permata_va |
constants.MethodMaybankVA |
maybank_va |
constants.MethodBNCVA |
bnc_va |
constants.MethodSampoernaVA |
sampoerna_va |
constants.MethodATMBersamaVA |
atm_bersama_va |
constants.MethodArthaGrahaVA |
artha_graha_va |
constants.MethodPaypal |
paypal |
| Constant | Value |
|---|---|
constants.StatusCompleted |
completed |
constants.StatusPending |
pending |
constants.StatusExpired |
expired |
constants.StatusCancelled |
cancelled |
constants.StatusCanceled |
canceled |
c := client.New("project", "api-key",
client.WithBaseURL("https://custom.api.com"), // Custom base URL (trailing slashes stripped)
client.WithTimeout(10 * time.Second), // HTTP timeout
client.WithHTTPClient(customHTTPClient), // Custom http.Client (shallow-copied)
client.WithLanguage(i18n.Indonesian), // Localized errors
client.WithRetries(5), // Retry attempts
client.WithRetryWait(500*time.Millisecond, 1*time.Minute), // Backoff config
client.WithBufferPool(customPool), // Custom buffer pool
client.WithMaxResponseSize(5 << 20), // Max response body (default 1 MB)
client.WithQRCodeOptions(qr.WithSize(512)), // QR code settings
)The qr package renders QRIS payment strings as PNG images. It can be used via the client or standalone:
// Via client (configured with WithQRCodeOptions)
png, err := c.QR().Encode(resp.Payment.PaymentNumber)
// Standalone
q := qr.New(qr.WithSize(512), qr.WithRecoveryLevel(qr.RecoveryHigh))
png, err := q.Encode(paymentNumber)Serve QR codes directly via HTTP with any framework:
// net/http
w.Header().Set("Content-Type", "image/png")
err := c.QR().Write(w, resp.Payment.PaymentNumber)Save QR codes to a file:
err := c.QR().WriteFile("payment_qr.png", resp.Payment.PaymentNumber)All QR methods return sentinel errors for programmatic handling via errors.Is:
| Sentinel | Condition |
|---|---|
qr.ErrEmptyContent |
Empty string passed to Encode, Write, or WriteFile |
qr.ErrEncodeFailed |
Underlying QR encoding failed (wraps underlying cause) |
| Option | Description | Default |
|---|---|---|
qr.WithSize(pixels) |
Image width/height in pixels | 256 |
qr.WithRecoveryLevel(level) |
Error correction level | RecoveryMedium |
qr.WithForegroundColor(color) |
QR module color | color.Black |
qr.WithBackgroundColor(color) |
Background color | color.White |
The grpc package provides server-side gRPC implementations that delegate to the SDK's REST-based services. Generated client stubs are included for consumers.
import (
"github.com/H0llyW00dzZ/pakasir-go-sdk/src/client"
pakasirv1 "github.com/H0llyW00dzZ/pakasir-go-sdk/src/grpc/pakasir/v1"
grpcsim "github.com/H0llyW00dzZ/pakasir-go-sdk/src/grpc/simulation"
grpctxn "github.com/H0llyW00dzZ/pakasir-go-sdk/src/grpc/transaction"
sdksim "github.com/H0llyW00dzZ/pakasir-go-sdk/src/simulation"
sdktxn "github.com/H0llyW00dzZ/pakasir-go-sdk/src/transaction"
"google.golang.org/grpc"
)
// SDK setup
c := client.New("my-project", "api-key")
txnSvc := grpctxn.NewService(sdktxn.NewService(c))
simSvc := grpcsim.NewService(sdksim.NewService(c))
// Register on any grpc.ServiceRegistrar
grpcServer := grpc.NewServer()
pakasirv1.RegisterTransactionServiceServer(grpcServer, txnSvc)
pakasirv1.RegisterSimulationServiceServer(grpcServer, simSvc)txn := pakasirv1.NewTransactionServiceClient(conn)
resp, err := txn.Create(ctx, &pakasirv1.CreateRequest{
OrderId: "INV-001",
Amount: 50000,
PaymentMethod: pakasirv1.PaymentMethod_PAYMENT_METHOD_QRIS,
})The services work with any gRPC interceptor chain (logging, auth, recovery, etc.) without SDK-specific middleware. SDK errors are automatically mapped to proper gRPC status codes (InvalidArgument, Unauthenticated, PermissionDenied, NotFound, AlreadyExists, ResourceExhausted, Unavailable, Canceled, DeadlineExceeded, Internal, Unknown).
The webhook package works with any Go HTTP framework via three entry points:
| Function | Input | Use With |
|---|---|---|
webhook.Parse(r) |
io.Reader |
Gin, Echo, any framework |
webhook.ParseRequest(r) |
*http.Request |
net/http, Chi |
webhook.ParseBytes(b) |
[]byte |
Fiber |
Both Parse and ParseRequest accept optional webhook.WithMaxBodySize(n) to override the default 1 MB body size limit.
All parse functions return sentinel errors for programmatic handling via errors.Is.
Each webhook sentinel wraps the corresponding central error from errors, so callers can match either:
| Sentinel | Condition |
|---|---|
webhook.ErrNilReader |
nil io.Reader passed to Parse (wraps errors.ErrNilReader) |
webhook.ErrNilRequest |
nil *http.Request or nil body passed to ParseRequest (wraps errors.ErrNilRequest) |
webhook.ErrEmptyBody |
empty payload passed to ParseBytes (wraps errors.ErrEmptyBody) |
webhook.ErrReadBody |
body read failure (wraps errors.ErrReadBody) |
webhook.ErrBodyTooLarge |
body exceeds configured size limit (wraps errors.ErrBodyTooLarge) |
webhook.ErrDecodeBody |
JSON decode failure (wraps errors.ErrDecodeBody) |
webhook.ErrInvalidOrderID |
Empty order ID from Event.Validate (wraps errors.ErrInvalidOrderID) |
webhook.ErrInvalidAmount |
Non-positive amount from Event.Validate (wraps errors.ErrInvalidAmount) |
// net/http
func webhookHandler(w http.ResponseWriter, r *http.Request) {
event, err := webhook.ParseRequest(r)
if err != nil {
http.Error(w, "bad request", http.StatusBadRequest)
return
}
// IMPORTANT: Validate amount and order_id against your system
if event.OrderID != expectedOrderID || event.Amount != expectedAmount {
http.Error(w, "mismatch", http.StatusBadRequest)
return
}
// Sandbox events should not trigger real fulfillment
if event.IsSandbox {
log.Println("sandbox webhook received, skipping fulfillment")
w.WriteHeader(http.StatusOK)
return
}
if event.Status == constants.StatusCompleted {
// Process the completed payment...
}
w.WriteHeader(http.StatusOK)
}The SDK provides sentinel errors for programmatic handling via errors.Is:
| Sentinel | Package | Condition |
|---|---|---|
errors.ErrInvalidProject |
errors |
Empty project slug |
errors.ErrInvalidAPIKey |
errors |
Empty API key |
errors.ErrInvalidOrderID |
errors |
Empty order ID |
errors.ErrInvalidAmount |
errors |
Non-positive amount |
errors.ErrInvalidPaymentMethod |
errors |
Unsupported payment method |
errors.ErrNilRequest |
errors |
Nil request pointer passed to a service method |
errors.ErrEncodeJSON |
errors |
JSON marshaling of a request body failed |
errors.ErrDecodeJSON |
errors |
JSON unmarshaling of a response body failed |
errors.ErrRequestFailed |
errors |
Permanent request failure (non-retryable) |
errors.ErrRequestFailedAfterRetries |
errors |
All retry attempts exhausted |
errors.ErrResponseTooLarge |
errors |
Response body exceeds configured size limit |
errors.ErrBodyTooLarge |
errors |
Request or webhook body exceeds configured size limit |
errors.ErrNilReader |
errors |
Nil reader passed to a parse function |
errors.ErrEmptyBody |
errors |
Empty payload |
errors.ErrReadBody |
errors |
Body read failure |
errors.ErrDecodeBody |
errors |
JSON decode failure of a webhook body |
webhook.ErrNilReader |
webhook |
nil io.Reader passed to Parse (wraps errors.ErrNilReader) |
webhook.ErrNilRequest |
webhook |
nil *http.Request or nil body passed to ParseRequest (wraps errors.ErrNilRequest) |
webhook.ErrEmptyBody |
webhook |
Empty payload (wraps errors.ErrEmptyBody) |
webhook.ErrReadBody |
webhook |
Body read failure (wraps errors.ErrReadBody) |
webhook.ErrBodyTooLarge |
webhook |
Body exceeds configured size limit (wraps errors.ErrBodyTooLarge) |
webhook.ErrDecodeBody |
webhook |
JSON decode failure (wraps errors.ErrDecodeBody) |
webhook.ErrInvalidOrderID |
webhook |
Empty order ID from Event.Validate (wraps errors.ErrInvalidOrderID) |
webhook.ErrInvalidAmount |
webhook |
Non-positive amount from Event.Validate (wraps errors.ErrInvalidAmount) |
qr.ErrEmptyContent |
qr |
Empty string passed to Encode, Write, or WriteFile |
qr.ErrEncodeFailed |
qr |
QR encoding failed (wraps underlying cause) |
url.ErrEmptyBaseURL |
url |
Empty base URL |
url.ErrEmptyProject |
url |
Empty project slug |
url.ErrEmptyOrderID |
url |
Empty order ID |
url.ErrInvalidAmount |
url |
Non-positive amount |
API error responses are returned as *errors.APIError and can be inspected via errors.As or the generic errors.AsType helper:
// Using AsType (recommended — no variable declaration needed)
if apiErr, ok := sdkerrors.AsType[*sdkerrors.APIError](err); ok {
fmt.Printf("Status: %d, Body: %s\n", apiErr.StatusCode, apiErr.Body)
}
// Using errors.As (standard library)
var apiErr *sdkerrors.APIError
if errors.As(err, &apiErr) {
fmt.Printf("Status: %d, Body: %s\n", apiErr.StatusCode, apiErr.Body)
}The SDK provides typed response structs with convenience methods for time parsing:
| Type | Helper Method | Description |
|---|---|---|
transaction.PaymentInfo |
ParseTime() |
Parse payment expiration timestamp |
transaction.TransactionInfo |
ParseTime() |
Parse transaction completion timestamp |
webhook.Event |
ParseTime() |
Parse webhook event completion timestamp |
// Parse expiration time from a create response
expiry, err := resp.Payment.ParseTime()
if err != nil {
log.Fatal(err)
}
fmt.Printf("Expires at: %s\n", expiry)Caution
The transaction.Service.Detail() method passes the API key as a URL query parameter. This is required by the Pakasir API specification — the Transaction Detail endpoint is a GET request where all parameters (including api_key) are in the query string.
This means the API key may be visible in:
- Server access logs (nginx, Apache, etc.)
- Reverse proxy / CDN logs (Cloudflare, HAProxy, etc.)
- Browser history (if called from a frontend context)
- Network monitoring tools
All other endpoints (Create, Cancel, Pay) use POST with the API key in the JSON request body, which is not logged by default.
Recommendations:
- Ensure your server and proxy configurations redact or exclude query strings from access logs when using the Detail endpoint.
- Rotate API keys periodically through the Pakasir dashboard.
- Never call the Detail endpoint from client-side / browser code.
This is an unofficial SDK. It is not affiliated with, endorsed by, or officially supported by Pakasir. This SDK is unofficial because the official API only provides documentation and support for their REST API and Node.js SDK. This library was created to add proper Go support, and it is actively used by the owner of this repository. Use at your own risk.
This project is licensed under the Apache License 2.0 — see the LICENSE file for details.