Go SDK quickstart

End-to-end: from go get to a working hosted checkout URL in about 40 lines of Go. We'll create a customer, open a checkout session for them, print the hosted URL, and read the session back.

Prerequisites

  • Go 1.22+ in your PATH.
  • A Plugipay test-mode key pair (pk_test_* + sk_test_*). Mint one in Settings → API keys or follow Portal → API keys.
  • The SDK on your module path: go get github.com/hachimi-cat/saas-plugipay/sdk/go (see Installation).

Set the credentials in your shell — every example on these pages assumes this:

export PLUGIPAY_KEY_ID=pk_test_xxxxxxxxxxxxxxxx
export PLUGIPAY_SECRET=sk_test_xxxxxxxxxxxxxxxxxxxxxxxx

The SDK reads both env vars by default, so a zero-value ClientOptions{} is enough when they're set. You can also pass them explicitly — useful in tests or when juggling multiple workspaces.

The 40-line round-trip

Create a fresh module:

mkdir plugipay-quickstart && cd plugipay-quickstart
go mod init example.com/plugipay-quickstart
go get github.com/hachimi-cat/saas-plugipay/sdk/go

Save as main.go:

package main

import (
    "context"
    "errors"
    "fmt"
    "log"
    "time"

    plugipay "github.com/hachimi-cat/saas-plugipay/sdk/go"
)

// ptr is the standard "make a pointer to a literal" helper. The SDK
// uses *string / *int for optional fields so callers can distinguish
// "not set" from "set to zero value".
func ptr[T any](v T) *T { return &v }

func main() {
    c, err := plugipay.NewClient(plugipay.ClientOptions{})
    if err != nil {
        log.Fatal(err) // missing PLUGIPAY_KEY_ID / PLUGIPAY_SECRET env
    }

    ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
    defer cancel()

    // 1. Create (or upsert) a customer.
    customer, err := c.Customers.Create(ctx, plugipay.CustomerCreateInput{
        Email: ptr("ada@example.com"),
        Name:  ptr("Ada Lovelace"),
    })
    if err != nil {
        fail(err)
    }
    fmt.Printf("Customer: %s  (%s)\n", customer.ID, deref(customer.Email))

    // 2. Open a hosted checkout session for them.
    session, err := c.CheckoutSessions.Create(ctx, plugipay.CheckoutSessionCreateInput{
        Amount:     125_000, // IDR 125,000 (minor units = rupiah)
        Currency:   plugipay.CurrencyIDR,
        Methods:    []plugipay.CheckoutMethod{plugipay.CheckoutMethodQRIS, plugipay.CheckoutMethodVA, plugipay.CheckoutMethodEwallet},
        SuccessURL: "https://yourapp.com/checkout/success",
        CancelURL:  "https://yourapp.com/checkout/cancel",
        CustomerID: &customer.ID,
    })
    if err != nil {
        fail(err)
    }
    fmt.Printf("Checkout URL: %s\n", session.HostedURL)
    fmt.Printf("Session id:   %s\n", session.ID)

    // 3. Read the session back to verify.
    fetched, err := c.CheckoutSessions.Get(ctx, session.ID)
    if err != nil {
        fail(err)
    }
    fmt.Printf("Status: %s\n", fetched.Status)
}

func fail(err error) {
    var pe *plugipay.Error
    if errors.As(err, &pe) {
        log.Fatalf("plugipay: %s %s — %s (requestId=%s)",
            pe.Code, pe.Message, pe.Error(), pe.RequestID)
    }
    log.Fatal(err)
}

func deref(p *string) string {
    if p == nil {
        return ""
    }
    return *p
}

Run it:

go run .

Expected output:

Customer: cus_01HXXXXXXXXXXXXXXXXXXXXXXX  (ada@example.com)
Checkout URL: https://plugipay.com/c/cs_01HXXXXXXXXXXXXXXXXXXXXXXX
Session id:   cs_01HXXXXXXXXXXXXXXXXXXXXXXX
Status: open

Open the checkout URL in a browser to see the hosted page in test mode — pick a method, simulate a payment, and the session status transitions to succeeded.

What just happened

1. The client

c, err := plugipay.NewClient(plugipay.ClientOptions{})

NewClient is the only constructor. With an empty ClientOptions{}, the SDK reads PLUGIPAY_KEY_ID, PLUGIPAY_SECRET, PLUGIPAY_BASE_URL, and PLUGIPAY_ON_BEHALF_OF from the environment. Missing creds → err is *plugipay.Error with Code: "missing_key_id" or "missing_secret".

Client is safe for concurrent use by multiple goroutines — construct one at startup and share it. Don't NewClient per request.

2. The context

ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()

Every method takes context.Context first. Three roles:

  • Cancellation. If the context is canceled (e.g. the inbound HTTP request is aborted), the SDK aborts the in-flight call. You get *plugipay.Error{Code: "canceled"}.
  • Per-call deadline. A WithTimeout here overrides ClientOptions.Timeout for this one call — useful when a default 30s is too long for an interactive flow.
  • Tracing. If you carry an OpenTelemetry span in the context, your HTTP client's transport sees it — pass a custom *http.Client with an instrumented transport.

If you don't need any of that, context.Background() is fine.

3. Create the customer

customer, err := c.Customers.Create(ctx, plugipay.CustomerCreateInput{
    Email: ptr("ada@example.com"),
    Name:  ptr("Ada Lovelace"),
})

CustomerCreateInput mirrors the API surface. Optional fields are pointer types — pass ptr("...") to set them, leave them nil to omit. The SDK serializes via encoding/json with the omitempty tags on the struct, so unset pointers don't appear on the wire.

Under the hood: HMAC-SHA256 over POST\n/api/v1/customers\n<timestamp>\nsha256(body)\n<idempotency-key>. The SDK generates a fresh idempotency key per Create call, so retrying the same Go statement at the application level doesn't create a duplicate.

The return type is *plugipay.Customer — a typed struct, not a map[string]any. Read fields directly:

customer.ID         // "cus_01HXX…"
customer.Email      // *string — may be nil
customer.AccountID  // "acc_01HYY…"
customer.CreatedAt  // ISO 8601 string

4. Open the checkout session

session, err := c.CheckoutSessions.Create(ctx, plugipay.CheckoutSessionCreateInput{
    Amount:     125_000,
    Currency:   plugipay.CurrencyIDR,
    Methods:    []plugipay.CheckoutMethod{plugipay.CheckoutMethodQRIS, ...},
    SuccessURL: "https://yourapp.com/checkout/success",
    CancelURL:  "https://yourapp.com/checkout/cancel",
    CustomerID: &customer.ID,
})

Three conventions worth noting:

  • Amount is int64 in minor units. For IDR that's rupiah (no decimals); for USD it'd be cents. 125_000Rp 125.000. See Concepts → Amounts.
  • Typed enums. Currency is a plugipay.CurrencyCode, Methods is []plugipay.CheckoutMethod. The values are still strings on the wire ("IDR", "qris"); the typed wrappers catch typos at compile time.
  • SuccessURL / CancelURL are string (required); CustomerID is *string (optional). The SDK serializes them as camelCase on the wire (successUrl, customerId).

Redirect your buyer to session.HostedURL. Don't link to the API URL — the hosted URL is the buyer-facing checkout page.

5. Read it back

fetched, err := c.CheckoutSessions.Get(ctx, session.ID)

Get methods take the resource id as a positional string argument. Status will be "open" immediately after creation and transitions through "succeeded", "failed", or "cancelled" as the buyer interacts.

6. Errors

var pe *plugipay.Error
if errors.As(err, &pe) {
    log.Fatalf("plugipay: %s %s — %s (requestId=%s)",
        pe.Code, pe.Message, pe.Error(), pe.RequestID)
}

Every failure — network timeout, bad credentials, 5xx, validation error, missing resource — returns *plugipay.Error wrapped through the standard error interface. Use errors.As to extract and branch on the stable .Code. See Errors for the full table and retry idioms.

Next steps

You now have:

  • A customer record (cus_…) you can charge again later.
  • A live hosted-checkout URL.
  • A pattern (create → act → Get to verify) that works for every other resource: invoices, subscriptions, refunds, payouts, plans.

Try these next:

  • Replace Methods with []plugipay.CheckoutMethod{plugipay.CheckoutMethodQRIS} and watch the hosted page collapse to a single QR — useful for in-person flows.
  • Switch to pk_live_* / sk_live_* keys and you're in production.
  • Wire the webhook listener so your server learns when the payment succeeds — redirects to SuccessURL aren't reliable on mobile.

Next

  • Authentication — env vars, platform keys, custom *http.Client.
  • Errors — the *plugipay.Error type and retry patterns.
  • Pagination — iterating Page[T] and cursors.
  • Webhooks — verify Plugipay → you deliveries.
  • Reference — every method on every resource.
Plugipay — Payments that don't tax your success