Documentation
¶
Overview ¶
Package qint is the official Go client for the Qint merchant API.
It is a thin, typed wrapper over the Qint public HTTP API: create payment intents, look them up, list them, and verify incoming webhook deliveries.
Quickstart ¶
client := qint.NewClient("qk_live_...")
intent, err := client.CreateIntent(ctx, qint.CreateIntentParams{
Amount: 19.90,
Currency: qint.CurrencyCHF,
Title: "Order #1024",
ReturnURL: "https://shop.example/thanks",
})
if err != nil {
// non-2xx responses are returned as *qint.QintError
}
// Redirect the buyer to intent.CheckoutURL to complete payment.
The default base URL is https://qint-api.fly.dev/api/v1 and can be overridden with WithBaseURL (an api.qint.ch host is planned).
See https://docs.qint.ch for the full API reference.
Example ¶
Example shows the 30-second flow: create an intent, then send the buyer to the hosted checkout.
package main
import (
"context"
"fmt"
"log"
qint "github.com/SwizzX-GmbH/qint-go"
)
func main() {
client := qint.NewClient("qk_live_...")
intent, err := client.CreateIntent(context.Background(), qint.CreateIntentParams{
Amount: 19.90,
Currency: qint.CurrencyCHF,
Title: "Order #1024",
ReturnURL: "https://shop.example/thanks",
})
if err != nil {
log.Fatal(err)
}
// Redirect the buyer to the hosted checkout to complete payment.
fmt.Println(intent.CheckoutURL)
}
Output:
Index ¶
Examples ¶
Constants ¶
const DefaultBaseURL = "https://qint-api.fly.dev/api/v1"
DefaultBaseURL is the production Qint merchant API endpoint. A dedicated api.qint.ch host is planned; override it with WithBaseURL when it lands.
const Version = "0.1.0"
Version is the semantic version of this SDK.
Variables ¶
This section is empty.
Functions ¶
func VerifyWebhookSignature ¶
VerifyWebhookSignature reports whether signatureHeader is a valid HMAC-SHA256 signature of rawBody produced with the endpoint's signing secret (whsec_...). signatureHeader is the raw X-Qint-Signature header value, i.e. "sha256=<hex>"; a bare "<hex>" is also accepted. The comparison is constant-time (crypto/hmac.Equal).
Always verify against the exact raw request body bytes — before any JSON decoding — and reject the delivery if this returns false. Use the X-Qint-Event-Id header to deduplicate re-deliveries of the same event.
Example (Handler) ¶
ExampleVerifyWebhookSignature_handler verifies an incoming webhook against the raw request body, then parses it. Deduplicate on the X-Qint-Event-Id header and always acknowledge quickly with a 2xx.
package main
import (
"io"
"net/http"
qint "github.com/SwizzX-GmbH/qint-go"
)
func main() {
const signingSecret = "whsec_..." // from the Qint dashboard
http.HandleFunc("/webhooks/qint", func(w http.ResponseWriter, r *http.Request) {
body, err := io.ReadAll(r.Body)
if err != nil {
w.WriteHeader(http.StatusBadRequest)
return
}
if !qint.VerifyWebhookSignature(body, r.Header.Get("X-Qint-Signature"), signingSecret) {
w.WriteHeader(http.StatusUnauthorized)
return
}
event, err := qint.ParseEvent(body)
if err != nil {
w.WriteHeader(http.StatusBadRequest)
return
}
eventID := r.Header.Get("X-Qint-Event-Id")
// TODO: skip if eventID was already processed (dedupe), then handle
// event.Status for event.IntentID.
_ = eventID
_ = event
w.WriteHeader(http.StatusOK) // ack fast
})
}
Output:
Types ¶
type Client ¶
type Client struct {
// contains filtered or unexported fields
}
Client is a typed client for the Qint merchant API. A Client is safe for concurrent use by multiple goroutines. Construct one with NewClient.
func NewClient ¶
NewClient returns a Client authenticated with the given API key (qk_live_...). Every request carries "Authorization: Bearer <apiKey>".
func (*Client) CreateIntent ¶
CreateIntent creates a new payment intent and returns it. Send the buyer to the returned Intent.CheckoutURL to complete payment. Requires an API key with the Write scope.
func (*Client) GetIntent ¶
GetIntent fetches a single payment intent by its id (pi_...). Requires the Read scope.
func (*Client) ListIntents ¶
func (c *Client) ListIntents(ctx context.Context, params ListIntentsParams) (*IntentList, error)
ListIntents returns one page of intents, most recent first. Requires the Read scope.
type CreateIntentParams ¶
type CreateIntentParams struct {
// Amount is the fiat amount to charge as a decimal, e.g. 19.90.
Amount float64 `json:"amount"`
// Currency is the settlement currency (CurrencyCHF, CurrencyEUR or
// CurrencyUSD).
Currency Currency `json:"currency"`
// Title is an optional human-readable description shown at checkout.
Title string `json:"title,omitempty"`
// IdempotencyKey, when set, makes CreateIntent safe to retry: the API
// replays the original intent (HTTP 200) instead of creating a duplicate.
IdempotencyKey string `json:"idempotencyKey,omitempty"`
// ReturnURL is an optional https URL (max 500 chars) the buyer is sent
// back to after checkout.
ReturnURL string `json:"returnUrl,omitempty"`
}
CreateIntentParams are the inputs to CreateIntent. Amount and Currency are required; the remaining fields are optional.
type Intent ¶
type Intent struct {
ID string `json:"id"`
Status IntentStatus `json:"status"`
Amount json.Number `json:"amount"`
Currency Currency `json:"currency"`
Title string `json:"title,omitempty"`
AssetSymbol string `json:"assetSymbol,omitempty"`
CryptoAmount string `json:"cryptoAmount,omitempty"`
DepositAddress string `json:"depositAddress,omitempty"`
CheckoutURL string `json:"checkoutUrl"`
ReturnURL string `json:"returnUrl,omitempty"`
CreatedAt time.Time `json:"createdAt"`
ExpiresAt time.Time `json:"expiresAt"`
ConfirmedAt *time.Time `json:"confirmedAt,omitempty"`
SettledAt *time.Time `json:"settledAt,omitempty"`
}
Intent is a Qint payment intent.
Amount is decoded as json.Number to preserve the exact decimal value the API returned (call Amount.String() or Amount.Float64()). CryptoAmount is an 8-decimal-place string. Optional timestamps are pointers that are nil until the intent reaches the corresponding state.
type IntentList ¶
type IntentList struct {
Items []Intent `json:"items"`
Total int `json:"total"`
Page int `json:"page"`
PageSize int `json:"pageSize"`
}
IntentList is one page of intents returned by ListIntents.
type IntentStatus ¶
type IntentStatus string
IntentStatus is the lifecycle status of a payment intent. The API always returns lowercase values.
const ( StatusInitiated IntentStatus = "initiated" StatusPending IntentStatus = "pending" StatusConfirmed IntentStatus = "confirmed" StatusSettled IntentStatus = "settled" StatusFailed IntentStatus = "failed" StatusExpired IntentStatus = "expired" StatusCancelled IntentStatus = "cancelled" )
The seven payment-intent statuses.
type ListIntentsParams ¶
type ListIntentsParams struct {
// Status, when set, filters to a single status.
Status IntentStatus
// Page is the 1-based page number (API default 1).
Page int
// PageSize is the page size (API default 20, max 100).
PageSize int
}
ListIntentsParams are the optional filters and pagination for ListIntents. A zero-valued field is omitted from the request, letting the API apply its defaults.
type Option ¶
type Option func(*Client)
Option configures a Client. Pass options to NewClient.
func WithBaseURL ¶
WithBaseURL overrides the API base URL (default DefaultBaseURL). A trailing slash is trimmed.
func WithHTTPClient ¶
WithHTTPClient sets a custom *http.Client, e.g. to configure proxies or a custom transport. A nil client is ignored. Overrides WithTimeout.
func WithTimeout ¶
WithTimeout sets the per-request timeout on the default HTTP client (default 30s). Ignored if WithHTTPClient supplied a client of your own.
type PaymentStatusEvent ¶
type PaymentStatusEvent struct {
Type string `json:"type"`
IntentID string `json:"intentId"`
Status IntentStatus `json:"status"`
Amount json.Number `json:"amount"`
Currency Currency `json:"currency"`
AssetSymbol string `json:"assetSymbol"`
InvoiceID string `json:"invoiceId,omitempty"`
PaymentLinkID string `json:"paymentLinkId,omitempty"`
OccurredAt time.Time `json:"occurredAt"`
Underpaid *bool `json:"underpaid,omitempty"`
ExpectedCryptoAmount string `json:"expectedCryptoAmount,omitempty"`
ReceivedCryptoAmount string `json:"receivedCryptoAmount,omitempty"`
}
PaymentStatusEvent is the JSON payload delivered to a merchant webhook endpoint. Its Type is always "payment.status".
Amount is decoded as json.Number to preserve the exact decimal value. Underpaid is a pointer so a missing field (nil) is distinguishable from an explicit false.
func ParseEvent ¶
func ParseEvent(rawBody []byte) (*PaymentStatusEvent, error)
ParseEvent unmarshals a webhook request body into a PaymentStatusEvent. Verify the signature with VerifyWebhookSignature first.
type QintError ¶
type QintError struct {
// StatusCode is the HTTP status code of the response.
StatusCode int
// Type is the problem-details "type" URI, if present.
Type string
// Title is the short, human-readable summary of the problem type.
Title string
// Detail is the human-readable explanation specific to this occurrence.
Detail string
// Body is the raw response body, retained for debugging.
Body string
}
QintError is returned for any non-2xx API response. It carries the HTTP status code together with the RFC 7807 problem-details fields returned by the API. Use errors.As to extract it:
var qerr *qint.QintError
if errors.As(err, &qerr) && qerr.StatusCode == 403 {
// missing scope, etc.
}