Skip to main content

Installation

go get github.com/digitalreceiptprotocol/drp-go
Requirements:
  • Go 1.19 or higher

Quick Start

Merchant Integration

package main

import (
    "context"
    "fmt"
    "log"
    "os"
    
    "github.com/digitalreceiptprotocol/drp-go"
)

func main() {
    // Initialize client
    client := drp.NewClient(
        os.Getenv("DRP_API_KEY"),
        "issuer-bank.com",
        drp.Production, // or drp.Sandbox
    )
    
    // Load merchant private key
    if err := client.LoadPrivateKeyFromFile("./merchant-private-key.pem"); err != nil {
        log.Fatal(err)
    }
    
    ctx := context.Background()
    
    // Send a receipt
    if err := sendReceipt(ctx, client, "tok_card_abc123", "txn_auth_456"); err != nil {
        log.Printf("Error sending receipt: %v", err)
    }
}

func sendReceipt(ctx context.Context, client *drp.Client, cardToken, txnID string) error {
    // 1. Get customer's public key
    keyData, err := client.PublicKeys.Get(ctx, cardToken, &drp.PublicKeyOptions{
        TransactionID: txnID,
    })
    if err != nil {
        return fmt.Errorf("failed to get public key: %w", err)
    }
    
    // 2. Build receipt data
    receipt := buildReceiptData() // Your receipt JSON-LD
    
    // 3. Send encrypted receipt (SDK handles signing and encryption)
    result, err := client.Receipts.Send(ctx, &drp.SendReceiptParams{
        ReceiptID:           fmt.Sprintf("rcpt_%d", time.Now().Unix()),
        Receipt:             receipt,
        CustomerPublicKey:   keyData.PublicKey,
        CustomerPublicKeyID: keyData.KeyID,
        CardLastFour:        cardToken[len(cardToken)-4:],
        TransactionID:       txnID,
        MerchantID:          "mch_coffee_shop_001",
        Amount:              12.50,
        Currency:            "USD",
    })
    if err != nil {
        if drp.IsCustomerNotEnrolled(err) {
            log.Println("Customer not enrolled - skip Vero receipt")
            return nil
        }
        return fmt.Errorf("failed to send receipt: %w", err)
    }
    
    log.Printf("Receipt sent: %s", result.DeliveryConfirmation)
    return nil
}

func buildReceiptData() map[string]interface{} {
    return map[string]interface{}{
        "@context": "https://schema.org",
        "@type":    "Receipt",
        "totalPaymentDue": map[string]interface{}{
            "@type":         "PriceSpecification",
            "price":         12.50,
            "priceCurrency": "USD",
        },
        // ... rest of receipt
    }
}

HTTP Server Integration

package main

import (
    "encoding/json"
    "log"
    "net/http"
    "os"
    
    "github.com/digitalreceiptprotocol/drp-go"
)

var drpClient *drp.Client

func init() {
    drpClient = drp.NewClient(
        os.Getenv("DRP_API_KEY"),
        "issuer-bank.com",
        drp.Production,
    )
    drpClient.LoadPrivateKeyFromFile("./merchant-private-key.pem")
}

type CheckoutRequest struct {
    CardToken     string                 `json:"card_token"`
    TransactionID string                 `json:"transaction_id"`
    Receipt       map[string]interface{} `json:"receipt"`
    Amount        float64                `json:"amount"`
}

func handleCheckout(w http.ResponseWriter, r *http.Request) {
    var req CheckoutRequest
    if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
        http.Error(w, err.Error(), http.StatusBadRequest)
        return
    }
    
    ctx := r.Context()
    
    // Get public key
    keyData, err := drpClient.PublicKeys.Get(ctx, req.CardToken, nil)
    if err != nil {
        log.Printf("Vero error: %v", err)
        json.NewEncoder(w).Encode(map[string]interface{}{
            "success": true,
            "drp":     false,
        })
        return
    }
    
    // Send receipt
    result, err := drpClient.Receipts.Send(ctx, &drp.SendReceiptParams{
        ReceiptID:           generateReceiptID(),
        Receipt:             req.Receipt,
        CustomerPublicKey:   keyData.PublicKey,
        CustomerPublicKeyID: keyData.KeyID,
        CardLastFour:        req.CardToken[len(req.CardToken)-4:],
        TransactionID:       req.TransactionID,
        MerchantID:          os.Getenv("MERCHANT_ID"),
        Amount:              req.Amount,
        Currency:            "USD",
    })
    if err != nil {
        log.Printf("Vero error: %v", err)
        json.NewEncoder(w).Encode(map[string]interface{}{
            "success": true,
            "drp":     false,
        })
        return
    }
    
    json.NewEncoder(w).Encode(map[string]interface{}{
        "success":      true,
        "confirmation": result.DeliveryConfirmation,
    })
}

func main() {
    http.HandleFunc("/checkout", handleCheckout)
    log.Fatal(http.ListenAndServe(":8080", nil))
}

Configuration

Client Options

import "github.com/digitalreceiptprotocol/drp-go"

// Basic client
client := drp.NewClient(
    "mch_live_abc123def456",  // API key
    "issuer-bank.com",         // Issuer domain
    drp.Production,            // Environment (Production or Sandbox)
)

// With custom options
client := drp.NewClientWithOptions(&drp.ClientOptions{
    APIKey:        "mch_live_abc123def456",
    IssuerDomain:  "issuer-bank.com",
    Environment:   drp.Production,
    Timeout:       30 * time.Second,
    MaxRetries:    3,
    Logger:        customLogger,
})

// Load private key
if err := client.LoadPrivateKeyFromFile("./merchant-private-key.pem"); err != nil {
    log.Fatal(err)
}

// Or set private key directly
privateKeyPEM := []byte(`-----BEGIN PRIVATE KEY-----...`)
if err := client.SetPrivateKey(privateKeyPEM); err != nil {
    log.Fatal(err)
}

API Reference

PublicKeys Methods

Get(ctx, cardToken, options)

keyData, err := client.PublicKeys.Get(
    ctx,
    "tok_card_abc123",
    &drp.PublicKeyOptions{
        TransactionID: "txn_auth_456",
    },
)
if err != nil {
    return err
}

fmt.Println(keyData.PublicKey)  // PEM-formatted public key
fmt.Println(keyData.KeyID)      // Key identifier
fmt.Println(keyData.ExpiresAt)  // Expiration time
Returns: *PublicKeyData, error

Receipts Methods

Send(ctx, params)

result, err := client.Receipts.Send(ctx, &drp.SendReceiptParams{
    ReceiptID:           "rcpt_unique_123",
    Receipt:             receiptData,
    CustomerPublicKey:   keyData.PublicKey,
    CustomerPublicKeyID: keyData.KeyID,
    CardLastFour:        "1234",
    TransactionID:       "txn_auth_456",
    MerchantID:          "mch_coffee_shop_001",
    Amount:              12.50,
    Currency:            "USD",
})
if err != nil {
    return err
}

fmt.Println(result.DeliveryConfirmation)
fmt.Println(result.IssuerSignature)
Returns: *ReceiptResponse, error

List(ctx, filters) - User Client Only

userClient := drp.NewUserClient(
    userOAuthToken,
    "your-bank.com",
)

response, err := userClient.Receipts.List(ctx, &drp.ReceiptsListOptions{
    StartDate:  "2025-11-01",
    EndDate:    "2025-12-07",
    MerchantID: "mch_coffee_shop_001",
    Limit:      20,
    Offset:     0,
})
if err != nil {
    return err
}

for _, receipt := range response.Receipts {
    fmt.Printf("Receipt from %s: $%.2f\n", receipt.MerchantName, receipt.Amount)
}
Returns: *ReceiptsListResponse, error

Merchants Methods

Register(ctx, data)

registration, err := drp.RegisterMerchant(ctx, &drp.RegistrationParams{
    RegistrationKey: "reg_key_xyz789",
    IssuerDomain:    "issuer-bank.com",
    MerchantID:      "mch_coffee_shop_001",
    MerchantName:    "Joe's Coffee Shop",
    ContactEmail:    "tech@coffeeshop.com",
    CallbackURL:     "https://coffeeshop.com/drp/webhook",
    PublicKey:       publicKeyPEM,
    BusinessAddress: &drp.Address{
        Street:     "123 Main St",
        City:       "Seattle",
        State:      "WA",
        PostalCode: "98101",
        Country:    "US",
    },
})
if err != nil {
    return err
}

// IMPORTANT: Save these securely!
fmt.Println(registration.APIKey)
fmt.Println(registration.WebhookSecret)
Returns: *RegistrationResponse, error

Crypto Utilities

// Encrypt data
encrypted, err := client.Crypto.Encrypt(
    []byte(receiptJSON),
    customerPublicKey,
)

// Decrypt data
decrypted, err := client.Crypto.Decrypt(
    encryptedData,
    userPrivateKey,
)

// Sign data
signature, err := client.Crypto.Sign(
    []byte(receiptJSON),
    merchantPrivateKey,
)

// Verify signature
valid, err := client.Crypto.Verify(
    []byte(receiptJSON),
    signature,
    merchantPublicKey,
)

Error Handling

import (
    "errors"
    "github.com/digitalreceiptprotocol/drp-go"
)

result, err := client.Receipts.Send(ctx, params)
if err != nil {
    // Check for specific error types
    var rateLimitErr *drp.RateLimitError
    var authErr *drp.AuthenticationError
    
    switch {
    case errors.As(err, &rateLimitErr):
        log.Printf("Rate limited. Retry after %d seconds", rateLimitErr.RetryAfter)
        time.Sleep(time.Duration(rateLimitErr.RetryAfter) * time.Second)
        // Retry logic
        
    case errors.As(err, &authErr):
        log.Fatal("Invalid API key")
        
    case drp.IsCustomerNotEnrolled(err):
        // Customer not enrolled - skip Vero receipt
        log.Println("Customer not enrolled")
        return nil
        
    case drp.IsValidationError(err):
        log.Printf("Validation error: %v", err)
        
    default:
        log.Printf("Vero error: %v", err)
        return err
    }
}

Error Types

type DRPError struct {
    Code    string
    Message string
    Details string
}

type RateLimitError struct {
    DRPError
    RetryAfter int
}

type AuthenticationError struct{ DRPError }
type ValidationError struct{ DRPError }
type NetworkError struct{ DRPError }
type EncryptionError struct{ DRPError }
type SignatureError struct{ DRPError }

Helper Functions

drp.IsCustomerNotEnrolled(err) bool
drp.IsRateLimitError(err) bool
drp.IsValidationError(err) bool
drp.IsAuthenticationError(err) bool

Context & Cancellation

All SDK methods accept context.Context for cancellation and timeouts:
import "context"

// With timeout
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()

result, err := client.Receipts.Send(ctx, params)
if err != nil {
    if errors.Is(err, context.DeadlineExceeded) {
        log.Println("Request timed out")
    }
    return err
}

// With cancellation
ctx, cancel := context.WithCancel(context.Background())

go func() {
    time.Sleep(2 * time.Second)
    cancel() // Cancel after 2 seconds
}()

result, err := client.Receipts.Send(ctx, params)

Concurrency

The Go SDK is thread-safe and designed for concurrent use:
var wg sync.WaitGroup

for _, transaction := range transactions {
    wg.Add(1)
    
    go func(txn Transaction) {
        defer wg.Done()
        
        if err := sendReceipt(ctx, client, txn.CardToken, txn.ID); err != nil {
            log.Printf("Error sending receipt for %s: %v", txn.ID, err)
        }
    }(transaction)
}

wg.Wait()

Connection Pooling

The SDK automatically manages HTTP connection pooling. Configure if needed:
import "net/http"

transport := &http.Transport{
    MaxIdleConns:        100,
    MaxIdleConnsPerHost: 10,
    IdleConnTimeout:     90 * time.Second,
}

client := drp.NewClientWithOptions(&drp.ClientOptions{
    APIKey:       os.Getenv("DRP_API_KEY"),
    IssuerDomain: "issuer-bank.com",
    HTTPClient:   &http.Client{Transport: transport},
})

Webhooks

Handle webhook events from issuers:
package main

import (
    "encoding/json"
    "io"
    "log"
    "net/http"
    "os"
    
    "github.com/digitalreceiptprotocol/drp-go/webhooks"
)

func handleWebhook(w http.ResponseWriter, r *http.Request) {
    signature := r.Header.Get("X-DRP-Signature")
    webhookSecret := os.Getenv("DRP_WEBHOOK_SECRET")
    
    // Read body
    body, err := io.ReadAll(r.Body)
    if err != nil {
        http.Error(w, err.Error(), http.StatusBadRequest)
        return
    }
    
    // Verify signature
    if !webhooks.Verify(body, signature, webhookSecret) {
        http.Error(w, "Invalid signature", http.StatusUnauthorized)
        return
    }
    
    // Parse event
    var event webhooks.Event
    if err := json.Unmarshal(body, &event); err != nil {
        http.Error(w, err.Error(), http.StatusBadRequest)
        return
    }
    
    // Handle event
    switch event.Type {
    case webhooks.ReceiptDelivered:
        log.Printf("Receipt %s delivered", event.ReceiptID)
        
    case webhooks.ReceiptDisputed:
        log.Printf("Receipt %s disputed: %s", 
            event.ReceiptID, 
            event.Details.DisputeReason,
        )
        // Handle dispute
    }
    
    w.WriteHeader(http.StatusOK)
    json.NewEncoder(w).Encode(map[string]bool{"received": true})
}

func main() {
    http.HandleFunc("/drp/webhook", handleWebhook)
    log.Fatal(http.ListenAndServe(":8080", nil))
}

Testing

Mock Client

package main

import (
    "testing"
    
    "github.com/digitalreceiptprotocol/drp-go/mocks"
    "github.com/stretchr/testify/assert"
    "github.com/stretchr/testify/mock"
)

func TestSendReceipt(t *testing.T) {
    // Create mock client
    mockClient := new(mocks.Client)
    
    // Set expectations
    mockClient.On("Send", mock.Anything, mock.Anything).Return(
        &drp.ReceiptResponse{
            Status:               "accepted",
            ReceiptID:            "rcpt_123",
            DeliveryConfirmation: "conf_456",
        },
        nil,
    )
    
    // Use mock in test
    result, err := mockClient.Send(ctx, params)
    
    assert.NoError(t, err)
    assert.Equal(t, "accepted", result.Status)
    mockClient.AssertExpectations(t)
}

Best Practices

Always pass context with timeout to prevent hanging requests:
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()

result, err := client.Receipts.Send(ctx, params)
Check for CustomerNotEnrolled explicitly - it’s not an error:
if drp.IsCustomerNotEnrolled(err) {
    // Normal case - skip Vero receipt
    return nil
}
Create one client instance and reuse it across requests. It manages connection pooling internally.
Send receipts concurrently but limit concurrency:
semaphore := make(chan struct{}, 10) // Max 10 concurrent

for _, txn := range transactions {
    semaphore <- struct{}{}
    go func(t Transaction) {
        defer func() { <-semaphore }()
        sendReceipt(ctx, client, t)
    }(txn)
}
Use environment variables or secret management (AWS Secrets Manager, HashiCorp Vault) for API keys and private keys.

Resources

GitHub Repository

Source code, examples, and issue tracking

Go Package

Package documentation

API Reference

Complete API documentation

Support

Get help with integration

Changelog

v1.0.0 (Latest)

  • Initial stable release
  • Full support for Vero API v1
  • Context support throughout
  • Thread-safe client
  • Connection pooling

v0.9.0 (Beta)

  • Beta release for testing
  • Core merchant and user APIs
See full changelog on GitHub.