Go SDK
Complete Go examples for integrating with Lorn AI.
Requirements
- Go 1.18+
Setup
// lorn/client.go
package lorn
import (
"bytes"
"encoding/json"
"fmt"
"io"
"net/http"
"net/url"
"os"
"time"
)
var (
DefaultBaseURL = getEnvOrDefault("LORN_BASE_URL", "https://{{YOUR_STORE_URL}}")
DefaultAPIKey = getEnvOrDefault("LORN_API_KEY", "{{YOUR_API_KEY}}")
)
func getEnvOrDefault(key, defaultValue string) string {
if value := os.Getenv(key); value != "" {
return value
}
return defaultValue
}
// Client is the Lorn AI API client
type Client struct {
BaseURL string
APIKey string
HTTPClient *http.Client
}
// NewClient creates a new Lorn client
func NewClient(baseURL, apiKey string) *Client {
if baseURL == "" {
baseURL = DefaultBaseURL
}
if apiKey == "" {
apiKey = DefaultAPIKey
}
return &Client{
BaseURL: baseURL,
APIKey: apiKey,
HTTPClient: &http.Client{
Timeout: 30 * time.Second,
},
}
}
// Request types
type SearchParams struct {
Q string `json:"q,omitempty"`
Category string `json:"category,omitempty"`
Tag string `json:"tag,omitempty"`
MinPrice *float64 `json:"min_price,omitempty"`
MaxPrice *float64 `json:"max_price,omitempty"`
Page int `json:"page,omitempty"`
PageSize int `json:"page_size,omitempty"`
}
type LineItemInput struct {
ProductID string `json:"product_id"`
VariantSKU string `json:"variant_sku,omitempty"`
Quantity int `json:"quantity,omitempty"`
}
type Address struct {
Name string `json:"name,omitempty"`
Line1 string `json:"line1,omitempty"`
Line2 string `json:"line2,omitempty"`
City string `json:"city,omitempty"`
State string `json:"state,omitempty"`
PostalCode string `json:"postal_code,omitempty"`
Country string `json:"country,omitempty"`
}
type Customer struct {
Email string `json:"email,omitempty"`
Phone string `json:"phone,omitempty"`
Name string `json:"name,omitempty"`
}
type CreateCheckoutInput struct {
Items []LineItemInput `json:"items"`
ShippingAddress *Address `json:"shipping_address,omitempty"`
Customer *Customer `json:"customer,omitempty"`
}
type UpdateCheckoutInput struct {
Items []LineItemInput `json:"items,omitempty"`
ShippingAddress *Address `json:"shipping_address,omitempty"`
Customer *Customer `json:"customer,omitempty"`
}
// Response types
type Product struct {
ID string `json:"id"`
Title string `json:"title"`
Description string `json:"description"`
Vendor string `json:"vendor,omitempty"`
Category string `json:"category,omitempty"`
Price float64 `json:"price"`
Currency string `json:"currency"`
Availability *Inventory `json:"availability,omitempty"`
Tags []string `json:"tags"`
Images []Image `json:"images"`
Variants []Variant `json:"variants"`
SimilarityScore *float64 `json:"similarity_score,omitempty"`
}
type Variant struct {
SKU string `json:"sku"`
Title string `json:"title,omitempty"`
Price float64 `json:"price"`
Currency string `json:"currency"`
Attributes map[string]string `json:"attributes"`
Inventory *Inventory `json:"inventory,omitempty"`
}
type Inventory struct {
Status string `json:"status,omitempty"`
Quantity int `json:"quantity,omitempty"`
}
type Image struct {
URL string `json:"url"`
AltText string `json:"alt_text,omitempty"`
}
type SearchResponse struct {
Items []Product `json:"items"`
Page int `json:"page"`
PageSize int `json:"page_size"`
Total int `json:"total"`
Version string `json:"version,omitempty"`
GeneratedAt string `json:"generated_at,omitempty"`
}
type CheckoutSession struct {
ID string `json:"id"`
Status string `json:"status"`
Currency string `json:"currency"`
LineItems []LineItem `json:"line_items"`
ShippingAddress *Address `json:"shipping_address,omitempty"`
Customer *Customer `json:"customer,omitempty"`
Shipping *Shipping `json:"shipping,omitempty"`
Tax *Tax `json:"tax,omitempty"`
Amounts *Amounts `json:"amounts,omitempty"`
ClientSecret string `json:"client_secret"`
CreatedAt string `json:"created_at"`
}
type LineItem struct {
ProductID string `json:"product_id"`
VariantSKU string `json:"variant_sku,omitempty"`
Quantity int `json:"quantity"`
Title string `json:"title,omitempty"`
UnitPrice float64 `json:"unit_price"`
Currency string `json:"currency"`
Subtotal float64 `json:"subtotal"`
}
type Shipping struct {
Method string `json:"method"`
Amount float64 `json:"amount"`
Currency string `json:"currency"`
}
type Tax struct {
Rate float64 `json:"rate"`
Amount float64 `json:"amount"`
Currency string `json:"currency"`
}
type Amounts struct {
Subtotal float64 `json:"subtotal"`
Tax float64 `json:"tax"`
Shipping float64 `json:"shipping"`
Total float64 `json:"total"`
Currency string `json:"currency"`
}
type CheckoutResponse struct {
CheckoutSession CheckoutSession `json:"checkout_session"`
}
type HealthResponse struct {
Status string `json:"status"`
Supabase string `json:"supabase"`
}
// API Methods
func (c *Client) Health() (*HealthResponse, error) {
var result HealthResponse
err := c.get("/health", nil, &result)
return &result, err
}
func (c *Client) SearchProducts(params SearchParams) (*SearchResponse, error) {
query := url.Values{}
if params.Q != "" {
query.Set("q", params.Q)
}
if params.Category != "" {
query.Set("category", params.Category)
}
if params.Tag != "" {
query.Set("tag", params.Tag)
}
if params.MinPrice != nil {
query.Set("min_price", fmt.Sprintf("%.2f", *params.MinPrice))
}
if params.MaxPrice != nil {
query.Set("max_price", fmt.Sprintf("%.2f", *params.MaxPrice))
}
if params.Page > 0 {
query.Set("page", fmt.Sprintf("%d", params.Page))
}
if params.PageSize > 0 {
query.Set("page_size", fmt.Sprintf("%d", params.PageSize))
}
var result SearchResponse
err := c.get("/acp/products", query, &result)
return &result, err
}
func (c *Client) GetProduct(productID string) (*Product, error) {
var result Product
err := c.get("/acp/products/"+productID, nil, &result)
return &result, err
}
func (c *Client) CreateCheckout(input CreateCheckoutInput) (*CheckoutResponse, error) {
var result CheckoutResponse
err := c.post("/checkout_sessions", input, &result)
return &result, err
}
func (c *Client) GetCheckout(sessionID string) (*CheckoutResponse, error) {
var result CheckoutResponse
err := c.get("/checkout_sessions/"+sessionID, nil, &result)
return &result, err
}
func (c *Client) UpdateCheckout(sessionID string, input UpdateCheckoutInput) (*CheckoutResponse, error) {
var result CheckoutResponse
err := c.patch("/checkout_sessions/"+sessionID, input, &result)
return &result, err
}
func (c *Client) CompleteCheckout(sessionID string) (*CheckoutResponse, error) {
var result CheckoutResponse
err := c.post("/checkout_sessions/"+sessionID+"/complete", nil, &result)
return &result, err
}
func (c *Client) CancelCheckout(sessionID string) (*CheckoutResponse, error) {
var result CheckoutResponse
err := c.post("/checkout_sessions/"+sessionID+"/cancel", nil, &result)
return &result, err
}
// HTTP helpers
func (c *Client) get(endpoint string, query url.Values, result interface{}) error {
urlStr := c.BaseURL + endpoint
if len(query) > 0 {
urlStr += "?" + query.Encode()
}
req, err := http.NewRequest("GET", urlStr, nil)
if err != nil {
return err
}
return c.doRequest(req, result)
}
func (c *Client) post(endpoint string, body interface{}, result interface{}) error {
return c.doWithBody("POST", endpoint, body, result)
}
func (c *Client) patch(endpoint string, body interface{}, result interface{}) error {
return c.doWithBody("PATCH", endpoint, body, result)
}
func (c *Client) doWithBody(method, endpoint string, body interface{}, result interface{}) error {
var bodyReader io.Reader
if body != nil {
jsonBody, err := json.Marshal(body)
if err != nil {
return err
}
bodyReader = bytes.NewBuffer(jsonBody)
}
req, err := http.NewRequest(method, c.BaseURL+endpoint, bodyReader)
if err != nil {
return err
}
if body != nil {
req.Header.Set("Content-Type", "application/json")
}
return c.doRequest(req, result)
}
func (c *Client) doRequest(req *http.Request, result interface{}) error {
req.Header.Set("Accept", "application/json")
req.Header.Set("X-ACP-API-Key", c.APIKey)
resp, err := c.HTTPClient.Do(req)
if err != nil {
return err
}
defer resp.Body.Close()
body, err := io.ReadAll(resp.Body)
if err != nil {
return err
}
if resp.StatusCode >= 400 {
return fmt.Errorf("API error: %d - %s", resp.StatusCode, string(body))
}
if result != nil {
return json.Unmarshal(body, result)
}
return nil
}Usage Examples
Search Products
package main
import (
"fmt"
"log"
"your-module/lorn"
)
func main() {
client := lorn.NewClient("", "")
// Basic search
results, err := client.SearchProducts(lorn.SearchParams{
Q: "running shoes",
})
if err != nil {
log.Fatal(err)
}
for _, product := range results.Items {
fmt.Printf("%s - $%.2f\n", product.Title, product.Price)
if product.SimilarityScore != nil {
fmt.Printf(" Relevance: %.2f\n", *product.SimilarityScore)
}
}
// With filters
maxPrice := 200.0
filtered, err := client.SearchProducts(lorn.SearchParams{
Q: "wireless headphones",
MaxPrice: &maxPrice,
PageSize: 5,
})
if err != nil {
log.Fatal(err)
}
fmt.Printf("Found %d products\n", filtered.Total)
}Get Product Details
package main
import (
"fmt"
"log"
"your-module/lorn"
)
func main() {
client := lorn.NewClient("", "")
product, err := client.GetProduct("prod_abc123")
if err != nil {
log.Fatal(err)
}
fmt.Printf("Title: %s\n", product.Title)
fmt.Printf("Price: $%.2f\n", product.Price)
fmt.Printf("Description: %s\n", product.Description)
// Show variants
for _, variant := range product.Variants {
attrs := ""
for k, v := range variant.Attributes {
attrs += fmt.Sprintf("%s: %s, ", k, v)
}
fmt.Printf(" - %s: %s - $%.2f\n", variant.SKU, attrs, variant.Price)
}
}Create Checkout
package main
import (
"fmt"
"log"
"your-module/lorn"
)
func main() {
client := lorn.NewClient("", "")
// Simple checkout
session, err := client.CreateCheckout(lorn.CreateCheckoutInput{
Items: []lorn.LineItemInput{
{ProductID: "prod_abc123", Quantity: 1},
},
})
if err != nil {
log.Fatal(err)
}
fmt.Printf("Session ID: %s\n", session.CheckoutSession.ID)
fmt.Printf("Total: $%.2f\n", session.CheckoutSession.Amounts.Total)
// Full checkout
fullSession, err := client.CreateCheckout(lorn.CreateCheckoutInput{
Items: []lorn.LineItemInput{
{
ProductID: "prod_abc123",
VariantSKU: "sku_black_m",
Quantity: 2,
},
},
ShippingAddress: &lorn.Address{
Name: "Jane Doe",
Line1: "123 Main Street",
City: "San Francisco",
State: "CA",
PostalCode: "94102",
Country: "US",
},
Customer: &lorn.Customer{
Email: "jane@example.com",
Name: "Jane Doe",
},
})
if err != nil {
log.Fatal(err)
}
fmt.Printf("Full checkout total: $%.2f\n", fullSession.CheckoutSession.Amounts.Total)
}Complete Checkout
package main
import (
"fmt"
"log"
"your-module/lorn"
)
func main() {
client := lorn.NewClient("", "")
order, err := client.CompleteCheckout("cs_demo_abc123")
if err != nil {
log.Fatal(err)
}
fmt.Printf("Status: %s\n", order.CheckoutSession.Status)
fmt.Printf("Total: $%.2f\n", order.CheckoutSession.Amounts.Total)
}Complete Purchase Flow
package main
import (
"fmt"
"log"
"your-module/lorn"
)
func main() {
client := lorn.NewClient("", "")
// 1. Search for products
fmt.Println("Searching for running shoes...")
maxPrice := 150.0
results, err := client.SearchProducts(lorn.SearchParams{
Q: "running shoes",
MaxPrice: &maxPrice,
})
if err != nil {
log.Fatal(err)
}
if len(results.Items) == 0 {
fmt.Println("No products found")
return
}
product := results.Items[0]
fmt.Printf("Found: %s - $%.2f\n", product.Title, product.Price)
// 2. Get product details
productDetails, err := client.GetProduct(product.ID)
if err != nil {
log.Fatal(err)
}
var variantSKU string
if len(productDetails.Variants) > 0 {
variantSKU = productDetails.Variants[0].SKU
}
// 3. Create checkout
fmt.Println("\nCreating checkout...")
session, err := client.CreateCheckout(lorn.CreateCheckoutInput{
Items: []lorn.LineItemInput{
{
ProductID: product.ID,
VariantSKU: variantSKU,
Quantity: 1,
},
},
ShippingAddress: &lorn.Address{
Name: "Jane Doe",
Line1: "123 Main St",
City: "San Francisco",
State: "CA",
PostalCode: "94102",
Country: "US",
},
Customer: &lorn.Customer{
Email: "jane@example.com",
},
})
if err != nil {
log.Fatal(err)
}
amounts := session.CheckoutSession.Amounts
fmt.Printf("Session: %s\n", session.CheckoutSession.ID)
fmt.Printf("Subtotal: $%.2f\n", amounts.Subtotal)
fmt.Printf("Tax: $%.2f\n", amounts.Tax)
fmt.Printf("Shipping: $%.2f\n", amounts.Shipping)
fmt.Printf("Total: $%.2f\n", amounts.Total)
// 4. Complete checkout
fmt.Println("\nCompleting purchase...")
order, err := client.CompleteCheckout(session.CheckoutSession.ID)
if err != nil {
log.Fatal(err)
}
fmt.Println("Order completed!")
fmt.Printf("Status: %s\n", order.CheckoutSession.Status)
}Error Handling
package main
import (
"fmt"
"strings"
"your-module/lorn"
)
func main() {
client := lorn.NewClient("", "")
_, err := client.GetProduct("invalid_id")
if err != nil {
errMsg := err.Error()
switch {
case strings.Contains(errMsg, "401"):
fmt.Println("Invalid API key")
case strings.Contains(errMsg, "404"):
fmt.Println("Product not found")
case strings.Contains(errMsg, "429"):
fmt.Println("Rate limited - retry later")
case strings.Contains(errMsg, "500"), strings.Contains(errMsg, "503"):
fmt.Println("Server error - retry later")
default:
fmt.Printf("Error: %s\n", errMsg)
}
}
}Webhook Signature Verification
package main
import (
"crypto/hmac"
"crypto/sha256"
"encoding/hex"
"fmt"
"strconv"
"strings"
"time"
)
const toleranceSeconds = 300
func VerifyWebhookSignature(body []byte, signatureHeader, secret string) bool {
// Parse signature header: "t=<timestamp>,v1=<signature>"
parts := strings.Split(signatureHeader, ",")
var timestamp int64
var receivedSig string
for _, part := range parts {
kv := strings.SplitN(part, "=", 2)
if len(kv) != 2 {
continue
}
switch kv[0] {
case "t":
timestamp, _ = strconv.ParseInt(kv[1], 10, 64)
case "v1":
receivedSig = kv[1]
}
}
// Check timestamp freshness
now := time.Now().Unix()
if abs(now-timestamp) > toleranceSeconds {
return false
}
// Compute expected signature
mac := hmac.New(sha256.New, []byte(secret))
mac.Write(body)
expectedSig := hex.EncodeToString(mac.Sum(nil))
// Timing-safe comparison
return hmac.Equal([]byte(expectedSig), []byte(receivedSig))
}
func abs(x int64) int64 {
if x < 0 {
return -x
}
return x
}
// HTTP handler example
/*
func webhookHandler(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("X-Webhook-Signature")
secret := os.Getenv("LORN_WEBHOOK_SECRET")
if !VerifyWebhookSignature(body, signature, secret) {
http.Error(w, "Invalid signature", http.StatusUnauthorized)
return
}
var event map[string]interface{}
json.Unmarshal(body, &event)
eventType := event["type"].(string)
switch eventType {
case "order.created":
fmt.Printf("New order: %v\n", event["data"])
case "order.fulfilled":
fmt.Printf("Order fulfilled: %v\n", event["data"])
}
w.Header().Set("Content-Type", "application/json")
w.Write([]byte(`{"received": true}`))
}
*/