DRP Go SDK
The official Go SDK for integrating the Digital Receipt Protocol into Go applications, microservices, and high-performance backends.Installation
Copy
go get github.com/digitalreceiptprotocol/drp-go
- Go 1.19 or higher
Quick Start
Merchant Integration
Copy
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 DRP 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
Copy
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("DRP 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("DRP 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
Copy
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)
Copy
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
*PublicKeyData, error
Receipts Methods
Send(ctx, params)
Copy
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)
*ReceiptResponse, error
List(ctx, filters) - User Client Only
Copy
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)
}
*ReceiptsListResponse, error
Merchants Methods
Register(ctx, data)
Copy
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: "[email protected]",
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)
*RegistrationResponse, error
Crypto Utilities
Copy
// 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
Copy
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 DRP receipt
log.Println("Customer not enrolled")
return nil
case drp.IsValidationError(err):
log.Printf("Validation error: %v", err)
default:
log.Printf("DRP error: %v", err)
return err
}
}
Error Types
Copy
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
Copy
drp.IsCustomerNotEnrolled(err) bool
drp.IsRateLimitError(err) bool
drp.IsValidationError(err) bool
drp.IsAuthenticationError(err) bool
Context & Cancellation
All SDK methods acceptcontext.Context for cancellation and timeouts:
Copy
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:Copy
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:Copy
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:Copy
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
Copy
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
Use Context for Timeouts
Use Context for Timeouts
Always pass context with timeout to prevent hanging requests:
Copy
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
result, err := client.Receipts.Send(ctx, params)
Handle Errors Properly
Handle Errors Properly
Check for
CustomerNotEnrolled explicitly - it’s not an error:Copy
if drp.IsCustomerNotEnrolled(err) {
// Normal case - skip DRP receipt
return nil
}
Reuse Client Instances
Reuse Client Instances
Create one client instance and reuse it across requests. It manages connection pooling internally.
Use Goroutines for Async Processing
Use Goroutines for Async Processing
Send receipts concurrently but limit concurrency:
Copy
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)
}
Store Keys Securely
Store Keys Securely
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 DRP API v1
- Context support throughout
- Thread-safe client
- Connection pooling
v0.9.0 (Beta)
- Beta release for testing
- Core merchant and user APIs