Skip to content

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

19 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

QBitFlow Go SDK

Go Version License: MPL-2.0

Official Go SDK for QBitFlow — a cryptocurrency payment processing platform supporting one-time payments, recurring subscriptions, refunds, and accounting exports.

Table of Contents

Installation

go get github.com/QBitFlow/qbitflow-go-sdk

Quick Start

package main

import (
    "fmt"
    "log"

    "github.com/QBitFlow/qbitflow-go-sdk"
    qbmodels "github.com/QBitFlow/qbitflow-go-sdk/pkg/models"
    qbf "github.com/QBitFlow/qbitflow-go-sdk/pkg/qbitflow"
)

func main() {
    client := qbitflow.New("your-api-key")

    // Create a one-time payment session
    session, err := client.Payments.CreateSession(&qbf.CreateSessionOptions{
        ProductName:  new("Premium Access"),
        Description:  new("One-time purchase"),
        Price:        new(49.99),
        SuccessURL:   new("https://yourapp.com/success"),
        CancelURL:    new("https://yourapp.com/cancel"),
        CustomerUUID: new("customer-uuid"),
    })
    if err != nil {
        log.Fatal(err)
    }

    fmt.Printf("Payment link: %s\n", session.Link)

    // Check transaction status
    status, err := client.TransactionStatus.GetTransactionStatus(
        session.UUID,
        qbmodels.TransactionTypeOneTimePayment,
    )
    if err != nil {
        log.Fatal(err)
    }
    fmt.Printf("Status: %s\n", status.Status)
}

Configuration

// Minimal — API key only
client := qbitflow.New("your-api-key")

// Custom configuration
import "time"

client := qbitflow.NewWithConfig(qbf.Config{
    APIKey:  "your-api-key",
    BaseURL: "https://api.qbitflow.app/v1", // optional, this is the default
    Timeout: 30 * time.Second,              // optional, default 30s
})

// Override base URL after creation (useful for testing)
client.SetBaseURL("http://localhost:3001")

One-Time Payments

Create a Payment Session

Provide either an existing ProductID or the (ProductName + Description + Price) triplet.

// Using an existing product
session, err := client.Payments.CreateSession(&qbf.CreateSessionOptions{
    ProductID:    new(uint64(42)),
    SuccessURL:   new("https://yourapp.com/success"),
    CancelURL:    new("https://yourapp.com/cancel"),
    CustomerUUID: new("customer-uuid"),
})

// Using an inline product definition
session, err := client.Payments.CreateSession(&qbf.CreateSessionOptions{
    ProductName: new("Custom Item"),
    Description: new("Limited edition widget"),
    Price:       new(19.99),
    SuccessURL:  new("https://yourapp.com/success"),
    CancelURL:   new("https://yourapp.com/cancel"),
})

Retrieve a Session

session, err := client.Payments.GetSession("session-uuid")
if err != nil {
    log.Fatal(err)
}

fmt.Printf("Product: %s  Price: $%.2f\n", session.ProductName, session.Price)
fmt.Printf("Is payment: %v\n", session.IsPayment())

Get a Completed Payment

payment, err := client.Payments.GetPayment("payment-uuid")
fmt.Printf("Amount: $%.2f  Hash: %s\n", payment.Amount, payment.TransactionHash)

List Payments

limit := uint16(20)

// One-time payments only
result, err := client.Payments.GetAllPayments(&limit, nil)

// Combined (one-time + subscription billing cycles)
combined, err := client.Payments.GetAllCombinedPayments(&limit, nil)

// Paginate
if result.HasMore() {
    nextPage, err := client.Payments.GetAllPayments(&limit, result.NextCursor)
    _ = nextPage
}

Get Customer for a Transaction

customer, err := client.Payments.GetCustomerForTransaction("transaction-uuid")
fmt.Printf("%s %s — %s\n", customer.Name, customer.LastName, customer.Email)

Subscriptions

Create a Subscription Session

minPeriods := uint32(3)

session, err := client.Subscriptions.CreateSession(&qbf.CreateSubscriptionSessionOptions{
    ProductID:   new(uint64(1)),
    Frequency:   qbmodels.Duration{Value: 1, Unit: qbmodels.DurationUnitMonths},
    TrialPeriod: &qbmodels.Duration{Value: 7, Unit: qbmodels.DurationUnitDays}, // optional
    MinPeriods:  &minPeriods,           // optional: minimum periods before cancellation
    SuccessURL:  new("https://yourapp.com/success"),
    CancelURL:   new("https://yourapp.com/cancel"),
    CustomerUUID: new("customer-uuid"),
})
fmt.Printf("Subscription link: %s\n", session.Link)

Available frequency units: DurationUnitSeconds, DurationUnitMinutes, DurationUnitHours, DurationUnitDays, DurationUnitWeeks, DurationUnitMonths.

Webhook URLs are configured in the dashboard, not per session. Session creation no longer accepts a webhookUrl. Set the Transaction webhook URL under settings in the QBitFlow dashboard — it applies consistently to every transaction.

Get Subscription Details

sub, err := client.Subscriptions.GetSubscription("subscription-uuid")
fmt.Printf("Status: %s  Next billing: %s\n",
    sub.SubscriptionStatus, sub.NextBillingDate.Format("2006-01-02"))

Subscription Status Changes

A subscription's status changes over its lifetime (trial, active, past_due, low_on_funds, pending, cancelled, trial_expired).

Previously you had to run a cron job that periodically fetched each subscription and diffed its status to react to these transitions. This is no longer necessary. Configure the Subscription status webhook URL under settings in the QBitFlow dashboard and QBitFlow will POST a SubscriptionStatusTransitionWebhook to your endpoint whenever a subscription changes status:

type SubscriptionStatusTransitionWebhook struct {
    SubscriptionUUID string             // the subscription that changed
    PreviousStatus   SubscriptionStatus // status before the transition
    CurrentStatus    SubscriptionStatus // status after the transition
    UpdatedAt        time.Time          // when the transition occurred
}

See Webhook Handling for a full handler example.

Subscription statuses: SubscriptionStatusTrial, SubscriptionStatusActive, SubscriptionStatusPastDue, SubscriptionStatusLowOnFunds, SubscriptionStatusPending, SubscriptionStatusCancelled, SubscriptionStatusTrialExpired.

Payment History

history, err := client.Subscriptions.GetPaymentHistory("subscription-uuid")
for _, record := range history {
    fmt.Printf("%s — $%.2f on %s\n", record.UUID, record.Amount,
        record.CreatedAt.Format("2006-01-02"))
}

Execute Test Billing Cycle (test mode only)

resp, err := client.Subscriptions.ExecuteTestBillingCycle("subscription-uuid")
fmt.Println(resp.Message)

Force Cancel a Subscription

resp, err := client.Subscriptions.ForceCancel("subscription-uuid")
fmt.Println(resp.Message)

Transaction Status

Poll Status

status, err := client.TransactionStatus.GetTransactionStatus(
    "transaction-uuid",
    qbmodels.TransactionTypeOneTimePayment,
)
if err != nil {
    log.Fatal(err)
}

switch status.Status {
case qbmodels.TransactionStatusCompleted:
    fmt.Printf("Done! tx hash: %s\n", status.TxHash)
case qbmodels.TransactionStatusFailed:
    fmt.Printf("Failed: %s\n", *status.Message)
}

Real-Time Status via WebSocket

The SDK returns the WebSocket URL; connect to it with your preferred WebSocket library.

wsURL := client.TransactionStatus.GetTransactionStatusWSURL(
    "transaction-uuid",
    qbmodels.TransactionTypeOneTimePayment,
)
fmt.Println("Connect to:", wsURL)
// e.g. github.com/gorilla/websocket

Transaction Types

Constant Value
TransactionTypeOneTimePayment "payment"
TransactionTypeCreateSubscription "createSubscription"
TransactionTypeCancelSubscription "cancelSubscription"
TransactionTypeExecuteSubscriptionPayment "executeSubscription"
TransactionTypeIncreaseAllowance "increaseAllowance"

Refunds

// Get the refund for a specific transaction (public)
refund, err := client.Refunds.GetByTransactionUUID("transaction-uuid")
fmt.Printf("Status: %s  Reason: %s\n", refund.Status, refund.Reason)

// List all active refunds
refunds, err := client.Refunds.GetAll()
fmt.Printf("%d active refunds\n", len(refunds))

// List inactive (processed/rejected) refunds with pagination
limit := uint16(20)
result, err := client.Refunds.GetAllInactive(&limit, nil)
for _, r := range result.Items {
    fmt.Printf("%s — %s\n", r.UUID, r.Status)
}

Refund statuses: RefundStatusPending, RefundStatusApproved, RefundStatusRejected, RefundStatusFailed.

Accounting Export

Export accounting events for a date range as JSON or CSV. Dates must be in YYYY-MM-DD format.

// JSON export — returns []AccountingEvent
events, err := client.Accounting.Export(qbf.AccountingExportParams{
    From:   "2025-01-01",
    To:     "2025-12-31",
    Format: "json",
})
if err != nil {
    log.Fatal(err)
}

for _, e := range events {
    fmt.Printf("[%s] %s — gross $%.2f net $%.2f\n",
        e.TxTimeUTC.Format("2006-01-02"), e.PaymentID,
        e.GrossAmountUSD, e.NetAmountUSD)
}

// CSV export — returns raw CSV string
csv, err := client.Accounting.Export(qbf.AccountingExportParams{
    From:   "2025-01-01",
    To:     "2025-12-31",
    Format: "csv",
})
if err != nil {
    log.Fatal(err)
}
os.WriteFile("accounting_2025.csv", []byte(csv), 0644)

Each AccountingEvent contains full transaction details: product, customer, blockchain info, fees (platform, organization, network), and gross/net amounts.

Account Claims

Organizations can create users before those users have claimed their accounts. During the unclaimed period all payments go to the organization and a ledger is kept. When the organization creates a claim request, the user receives a link to set up their password and wallet, then the owed funds are transferred.

// Create a claim request for a user (admin only) — returns the claim link
resp, err := client.Claims.CreateClaimRequest(userID)
fmt.Printf("Share this link with the user: %s\n", resp.Link)

// Get the existing claim request for a user (admin only) — same response as CreateClaimRequest
resp, err = client.Claims.GetClaimRequestByUserID(userID)
fmt.Printf("Existing claim link: %s\n", resp.Link)

// List funds the org owes to recently claimed users
funds, err := client.Claims.GetClaimFunds()
for _, f := range funds {
    fmt.Printf("User %d owes $%.2f\n", f.UserID, f.TotalAmountOwed)
}

// Trigger test claim fund computation for a user (test mode only)
resp2, err := client.Claims.TriggerTestClaimFunds(userID)
fmt.Println(resp2.Message)

Customer Management

// Create
customer, err := client.Customers.Create(&qbf.CreateCustomer{
    Name:     "Jane",
    LastName: "Doe",
    Email:    "jane@example.com",
    Reference: new("CRM-42"),
})

// Get by UUID or email
customer, err = client.Customers.Get("customer-uuid")
customer, err = client.Customers.GetByEmail("jane@example.com")

// List (paginated)
limit := uint16(50)
page, err := client.Customers.GetAll(&limit, nil)
if page.HasMore() {
    next, err := client.Customers.GetAll(&limit, page.NextCursor)
    _ = next
}

// Update — UUID is the first argument, not a field on the struct
updated, err := client.Customers.Update("customer-uuid", &qbf.UpdateCustomer{
    Name:     "Jane",
    LastName: "Smith",
    Email:    "jane.smith@example.com",
})

// Delete
err = client.Customers.Delete("customer-uuid")

Product Management

ref := "PROD-001"

// Create
product, err := client.Products.Create(&qbf.CreateProduct{
    Name:        "Premium Plan",
    Description: "Access to all features",
    Price:       29.99,
    Reference:   &ref, // optional, auto-generated if omitted
})

// Get
product, err = client.Products.Get(product.ID)
product, err = client.Products.GetByReference("PROD-001")

// List all
products, err := client.Products.GetAll()

// Update
updated, err := client.Products.Update(product.ID, &qbf.UpdateProduct{
    Name:        "Premium Plus",
    Description: "Enhanced features",
    Price:       39.99,
})

// Delete
err = client.Products.Delete(product.ID)

User Management

// Create (admin only)
user, err := client.Users.Create(&qbf.CreateUser{
    Name:               "Alice",
    LastName:           "Smith",
    Email:              "alice@example.com",
    Role:               "user", // "user" or "admin"
    OrganizationFeeBps: 100,   // 1% fee, optional
})

// Get current user (from API key)
me, err := client.Users.Get()

// Get by ID (admin only)
user, err = client.Users.GetByID(42)

// List all (admin only)
users, err := client.Users.GetAll()

// Update
updated, err := client.Users.Update(user.ID, &qbf.UpdateUser{
    Name:     "Alicia",
    LastName: "Smith",
    Email:    user.Email,
})

// Delete (admin only)
err = client.Users.Delete(user.ID)

API Key Management

// Create
resp, err := client.ApiKeys.Create(&qbf.CreateApiKeyDto{
    Name:   "Production Key",
    UserID: userID,
    Test:   false,
})
fmt.Printf("Key (only shown once): %s\n", resp.Key)

// List for current user
keys, err := client.ApiKeys.GetAll()

// List for a specific user (admin only)
keys, err = client.ApiKeys.GetForUser(userID)

// Delete
err = client.ApiKeys.Delete(keyID)

Webhook Handling

Webhook destination URLs are configured under settings in the QBitFlow dashboard, not in code. There are two independent webhooks you can enable:

Dashboard setting Payload type Fired when
Transaction webhook SessionWebhookResponse a payment or subscription transaction changes status
Subscription status webhook SubscriptionStatusTransitionWebhook a subscription transitions between statuses (e.g. trialactive, activepast_due)

Every webhook is signed with HMAC-SHA256. Verify it with client.Webhooks.Verify before trusting the payload. Responding with a status >= 400 tells QBitFlow to retry delivery.

Webhook headers: X-Webhook-Signature-256, X-Webhook-Timestamp, X-Webhook-ID.

Test Webhook Reachability

The dashboard's "Test webhook" action sends a fake payload to your configured URL to confirm the endpoint is reachable. This payload may not match a real webhook's shape, so your handler should short-circuit it: when the incoming X-Webhook-ID header equals qbf.TEST_WEBHOOK_ID, return HTTP 200 immediately and skip normal processing.

Transaction Webhook Handler

package main

import (
    "encoding/json"
    "io"
    "net/http"

    "github.com/QBitFlow/qbitflow-go-sdk"
    qbmodels "github.com/QBitFlow/qbitflow-go-sdk/pkg/models"
    qbf "github.com/QBitFlow/qbitflow-go-sdk/pkg/qbitflow"
)

var sdkClient = qbitflow.New("your-api-key")

func transactionWebhookHandler(w http.ResponseWriter, r *http.Request) {
    body, err := io.ReadAll(r.Body)
    if err != nil {
        http.Error(w, "bad request", http.StatusBadRequest)
        return
    }

    signature := r.Header.Get(sdkClient.Webhooks.GetSignatureHeader())
    timestamp := r.Header.Get(sdkClient.Webhooks.GetTimestampHeader())
    webhookID := r.Header.Get(sdkClient.Webhooks.GetWebhookIDHeader())

    // Verify authenticity — a >= 400 response causes QBitFlow to retry.
    valid, err := sdkClient.Webhooks.Verify(body, signature, timestamp)
    if err != nil || !valid {
        http.Error(w, "unauthorized", http.StatusUnauthorized)
        return
    }

    // Reachability check from the dashboard "Test webhook" action.
    if webhookID == qbf.TEST_WEBHOOK_ID {
        w.WriteHeader(http.StatusOK)
        return
    }

    var event qbmodels.SessionWebhookResponse
    if err := json.Unmarshal(body, &event); err != nil {
        http.Error(w, "bad payload", http.StatusBadRequest)
        return
    }

    switch event.Status.Status {
    case qbmodels.TransactionStatusCompleted:
        // Fulfill order, update DB, etc.
    case qbmodels.TransactionStatusFailed:
        // Notify customer
    }

    w.WriteHeader(http.StatusOK)
}

Subscription Status Webhook Handler

Configure the Subscription status webhook in the dashboard to receive lifecycle transitions. This replaces polling each subscription on a cron schedule to detect status changes.

func subscriptionStatusWebhookHandler(w http.ResponseWriter, r *http.Request) {
    body, err := io.ReadAll(r.Body)
    if err != nil {
        http.Error(w, "bad request", http.StatusBadRequest)
        return
    }

    signature := r.Header.Get(sdkClient.Webhooks.GetSignatureHeader())
    timestamp := r.Header.Get(sdkClient.Webhooks.GetTimestampHeader())
    webhookID := r.Header.Get(sdkClient.Webhooks.GetWebhookIDHeader())

    valid, err := sdkClient.Webhooks.Verify(body, signature, timestamp)
    if err != nil || !valid {
        http.Error(w, "unauthorized", http.StatusUnauthorized)
        return
    }

    // Reachability check from the dashboard "Test webhook" action.
    if webhookID == qbf.TEST_WEBHOOK_ID {
        w.WriteHeader(http.StatusOK)
        return
    }

    var event qbmodels.SubscriptionStatusTransitionWebhook
    if err := json.Unmarshal(body, &event); err != nil {
        http.Error(w, "bad payload", http.StatusBadRequest)
        return
    }

    switch event.CurrentStatus {
    case qbmodels.SubscriptionStatusActive:
        // Trial converted or payment recovered — grant/keep access.
    case qbmodels.SubscriptionStatusPastDue, qbmodels.SubscriptionStatusLowOnFunds:
        // Prompt the customer to top up their allowance.
    case qbmodels.SubscriptionStatusCancelled, qbmodels.SubscriptionStatusTrialExpired:
        // Revoke access.
    }

    w.WriteHeader(http.StatusOK)
}

Error Handling

import qberrors "github.com/QBitFlow/qbitflow-go-sdk/pkg/errors"

payment, err := client.Payments.GetPayment("uuid")
if err != nil {
    switch e := err.(type) {
    case *qberrors.NotFoundError:
        fmt.Println("not found")
    case *qberrors.ValidationError:
        fmt.Printf("validation: %s\n", e.Message)
    case *qberrors.QBitFlowError:
        fmt.Printf("API error %d: %s\n", e.StatusCode, e.Message)
    default:
        fmt.Printf("unexpected: %v\n", err)
    }
}

License

This project is licensed under the MPL-2.0 License - see the LICENSE file for details.

Support

Changelog

See CHANGELOG.md for version history.

Security

For security issues, please email security@qbitflow.app instead of using the issue tracker.

About

Official Go SDK for QBitFlow - Next Generation Crypto Payment Processing

Resources

Security policy

Stars

Watchers

Forks

Releases

Packages

Contributors

Languages