Checkout sessions
A checkout session is a one-time, hosted payment intent. You create one with an amount, a currency, and an allow-list of payment methods; Plugipay returns an object with a HostedURL you redirect the customer to. When they pay — or, for offline methods, when you confirm — the session transitions to completed and emits a webhook. The Go SDK exposes the five checkout-session endpoints behind client.CheckoutSessions; every method takes context.Context first and returns (*plugipay.CheckoutSession, error) or (plugipay.Page[plugipay.CheckoutSession], error). For wire shapes, the full status lifecycle, and per-method validation rules, see API → Checkout sessions.
Field on the Client
client.CheckoutSessions — type *plugipay.CheckoutSessionsResource. Installed by NewClient; shares the parent *http.Client, base URL, key, and OnBehalfOf default. Safe for concurrent use.
Methods
Create
Signature. func (r *CheckoutSessionsResource) Create(ctx context.Context, in CheckoutSessionCreateInput) (*CheckoutSession, error)
Opens a hosted checkout. Required fields: Amount, Currency, Methods, SuccessURL, CancelURL. LineItems is required by the API but the SDK normalizes a nil slice to [] for you, so passing none is fine. The SDK auto-mints an Idempotency-Key.
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
sess, err := client.CheckoutSessions.Create(ctx, plugipay.CheckoutSessionCreateInput{
Amount: 199_000_00, // IDR 199,000
Currency: plugipay.CurrencyIDR,
Methods: []plugipay.CheckoutMethod{
plugipay.CheckoutMethodQRIS,
plugipay.CheckoutMethodVA,
},
SuccessURL: "https://example.com/thank-you",
CancelURL: "https://example.com/cart",
LineItems: []plugipay.CheckoutSessionLineItem{
{Name: "Annual Pro plan", Quantity: 1, UnitAmount: 199_000_00},
},
Metadata: map[string]string{"order_id": "ord_42"},
})
if err != nil {
return err
}
// Redirect the buyer to sess.HostedURL.
http.Redirect(w, r, sess.HostedURL, http.StatusSeeOther)
One session, at most one payment. A session resolves to zero or one payments. For recurring billing use Subscriptions; for multi-charge flows, create one session per charge.
Get
Signature. func (r *CheckoutSessionsResource) Get(ctx context.Context, id string) (*CheckoutSession, error)
Fetch a session by id. The current Status tells you where the buyer is in the funnel. Naturally idempotent.
sess, err := client.CheckoutSessions.Get(ctx, "cs_01HX...")
if err != nil {
return err
}
switch sess.Status {
case "completed":
// payment captured
case "expired", "canceled":
// terminal failures
case "open", "pending", "pending_review":
// still live
}
List
Signature. func (r *CheckoutSessionsResource) List(ctx context.Context, params CheckoutSessionListParams) (Page[CheckoutSession], error)
Cursor-paginated. Filters: Status (string pointer — one of the lifecycle values) and CustomerID (filter to one buyer).
status := "completed"
custID := "cus_01HX..."
page, err := client.CheckoutSessions.List(ctx, plugipay.CheckoutSessionListParams{
Status: &status,
CustomerID: &custID,
})
if err != nil {
return err
}
for _, s := range page.Data {
fmt.Println(s.ID, s.Status, s.Amount)
}
Cancel
Signature. func (r *CheckoutSessionsResource) Cancel(ctx context.Context, id string) (*CheckoutSession, error)
Voids an open, pending, or pending_review session. The hosted URL stops accepting payment immediately and serves a cancellation screen. Terminal sessions (completed / expired / canceled) reject the call with 409 conflict. Auto-keyed for idempotency — retrying after a transient failure is safe.
sess, err := client.CheckoutSessions.Cancel(ctx, "cs_01HX...")
if err != nil {
var pe *plugipay.Error
if errors.As(err, &pe) && pe.Code == "conflict" {
// already terminal — treat as already-canceled
return nil
}
return err
}
log.Printf("canceled %s", sess.ID)
Confirm
Signature. func (r *CheckoutSessionsResource) Confirm(ctx context.Context, id string) (*CheckoutSession, error)
Finalizes a session in flows where the buyer pays inline (typically the manual adapter — bank transfer, cash, EDC slip). Most teams never call this; the hosted page calls it on the buyer's behalf. Not auto-keyed — the operation is naturally idempotent server-side, but if you need belt-and-suspenders, drop to client.Do(...) with your own key.
sess, err := client.CheckoutSessions.Confirm(ctx, "cs_01HX...")
if err != nil {
return err
}
log.Printf("confirmed → %s", sess.Status) // → "completed"
Types
type CheckoutSession struct {
ID string `json:"id"` // "cs_..."
AccountID string `json:"accountId"`
CustomerID *string `json:"customerId"` // nil for guest
Amount int64 `json:"amount"`
Currency CurrencyCode `json:"currency"`
Status string `json:"status"`
Methods []CheckoutMethod `json:"methods"`
Adapter *string `json:"adapter"` // routed provider
LineItems json.RawMessage `json:"lineItems"` // opaque to SDK
SuccessURL string `json:"successUrl"`
CancelURL string `json:"cancelUrl"`
HostedURL string `json:"hostedUrl"` // redirect target
ExpiresAt string `json:"expiresAt"`
CompletedAt *string `json:"completedAt"`
Metadata map[string]string `json:"metadata"`
CreatedAt string `json:"createdAt"`
UpdatedAt string `json:"updatedAt"`
}
type CheckoutSessionLineItem struct {
Name string `json:"name"`
Quantity int64 `json:"quantity"`
UnitAmount int64 `json:"unitAmount"` // smallest currency unit
}
CheckoutMethod is a string type with constants CheckoutMethodQRIS, CheckoutMethodVA, CheckoutMethodEwallet, CheckoutMethodCard, CheckoutMethodRetail, CheckoutMethodPaypal. LineItems on the returned struct is json.RawMessage because the wire shape evolves — decode it into your own slice if you need to inspect it. For the full status lifecycle and adapter routing rules: API → Checkout sessions.
Common patterns
Idempotent cart-to-session
The SDK's auto-keyed idempotency is per-call — a retry of the same Go statement mints a new key. If you want to dedupe by your own cart id (so a double-click on "Pay" never creates two sessions), use the low-level Do:
func createSessionForCart(ctx context.Context, c *plugipay.Client, cartID string, in plugipay.CheckoutSessionCreateInput) (*plugipay.CheckoutSession, error) {
if in.LineItems == nil {
in.LineItems = []plugipay.CheckoutSessionLineItem{}
}
var out plugipay.CheckoutSession
err := c.Do(ctx, plugipay.RequestOptions{
Method: "POST",
Path: "/api/v1/checkout-sessions",
Body: in,
IdempotencyKey: "cart_" + cartID, // your stable key
}, &out)
if err != nil {
return nil, err
}
return &out, nil
}
The first call creates the session and stores the key; a retry within 24 hours returns the same session.
Polling for completion
For asynchronous methods (VA, retail) you can poll — though webhooks are recommended:
func pollUntilTerminal(ctx context.Context, c *plugipay.Client, id string) (*plugipay.CheckoutSession, error) {
backoff := time.Second
for {
sess, err := c.CheckoutSessions.Get(ctx, id)
if err != nil {
return nil, err
}
switch sess.Status {
case "completed", "expired", "canceled":
return sess, nil
}
select {
case <-ctx.Done():
return nil, ctx.Err()
case <-time.After(backoff):
}
if backoff < 30*time.Second {
backoff *= 2
}
}
}
Pair with Webhooks for production — polling is fine for tests but spends compute.
Struct opts for optional fields
func ptr[T any](v T) *T { return &v }
custID := "cus_01HX..."
expires := 30 * 60 // 30 minutes
sess, err := c.CheckoutSessions.Create(ctx, plugipay.CheckoutSessionCreateInput{
Amount: 49_99,
Currency: plugipay.CurrencyUSD,
Methods: []plugipay.CheckoutMethod{plugipay.CheckoutMethodCard},
SuccessURL: "https://example.com/ok",
CancelURL: "https://example.com/no",
CustomerID: &custID,
ExpiresInSec: &expires,
})
Errors
Code |
Status |
Cause |
|---|---|---|
validation_error |
400 | Amount <= 0, bad URL scheme, unknown method. |
not_found |
404 | Session id doesn't exist or is in another workspace. |
conflict |
409 | Cancel/Confirm on a terminal session. |
adapter_not_configured |
422 | Workspace has no adapter capable of these Methods. |
insufficient_scope |
403 | Key lacks plugipay:checkout:write. |
var pe *plugipay.Error
if errors.As(err, &pe) {
switch pe.Code {
case "adapter_not_configured":
// surface "configure Xendit / Midtrans first" to the merchant
case "validation_error":
log.Printf("bad input: %s (requestId=%s)", pe.Message, pe.RequestID)
}
}
Next
- Webhooks — receive
checkout_session.completed. - Refunds — refund a completed session.
- Receipts — auto-generated receipt for a completed session.
- API → Checkout sessions — HTTP-level reference.