A Go library for reading, writing, and validating Payment Automation Manager (PAM) Standard Payment Request (SPR) files used by the U.S. Treasury Bureau of the Fiscal Service for federal agency payment processing.
PAM SPR files are used by federal agencies to process various types of payments including:
The library supports both ACH (Automated Clearing House) and Check payment methods, with comprehensive validation for Same Day ACH requirements and agency-specific rules.
go get github.com/moov-io/pamspr
Or install the CLI tool:
go install github.com/moov-io/pamspr/cmd/pamspr@latest
package main import ( "fmt" "log" "os" "github.com/moov-io/pamspr/pkg/pamspr" ) func main() { // Open the file file, err := os.Open("payments.spr") if err != nil { log.Fatal(err) } defer file.Close() // Parse the file reader := pamspr.NewReader(file) pamFile, err := reader.Read() if err != nil { log.Fatal(err) } // Display file information fmt.Printf("Input System: %s\n", pamFile.Header.InputSystem) fmt.Printf("Total Payments: %d\n", pamFile.Trailer.TotalCountPayments) fmt.Printf("Total Amount: $%.2f\n", float64(pamFile.Trailer.TotalAmountPayments)/100) }Creating an ACH Payment File
package main import ( "log" "os" "github.com/moov-io/pamspr/pkg/pamspr" ) func main() { // Build file using fluent API builder := pamspr.NewFileBuilder() file, err := builder. WithHeader("MY_AGENCY_SYSTEM", "502", false). StartACHSchedule("SCH001", "Salary", "12345678", "PPD"). AddACHPayment(&pamspr.ACHPayment{ AgencyAccountIdentifier: "EMP001", Amount: 250000, // $2,500.00 AgencyPaymentTypeCode: "S", IsTOPOffset: "1", PayeeName: "JOHN DOE", PayeeAddressLine1: "123 MAIN ST", CityName: "WASHINGTON", StateCodeText: "DC", PostalCode: "20001", RoutingNumber: "021000021", AccountNumber: "1234567890", ACHTransactionCode: "22", // Checking Credit PaymentID: "PAY001", TIN: "123456789", PaymentRecipientTINIndicator: "1", // SSN }). Build() if err != nil { log.Fatal(err) } // Write to file output, err := os.Create("ach_payments.spr") if err != nil { log.Fatal(err) } defer output.Close() writer := pamspr.NewWriter(output) if err := writer.Write(file); err != nil { log.Fatal(err) } fmt.Println("ACH payment file created successfully!") }Creating a Check Payment File
package main import ( "log" "os" "github.com/moov-io/pamspr/pkg/pamspr" ) func main() { builder := pamspr.NewFileBuilder() file, err := builder. WithHeader("CHECK_SYSTEM", "502", false). StartCheckSchedule("CHK001", "Vendor", "87654321", "stub"). AddCheckPayment(&pamspr.CheckPayment{ AgencyAccountIdentifier: "VEND001", Amount: 150000, // $1,500.00 AgencyPaymentTypeCode: "V", IsTOPOffset: "1", PayeeName: "ACME CORP", PayeeAddressLine1: "789 BUSINESS BLVD", PayeeAddressLine2: "SUITE 100", CityName: "NEW YORK", StateCodeText: "NY", PostalCode: "10001", CheckLegendText1: "INVOICE #12345", CheckLegendText2: "PO #67890", PaymentID: "CHK001", TIN: "123456789", PaymentRecipientTINIndicator: "2", // EIN Stub: &pamspr.CheckStub{ RecordCode: "13", PaymentID: "CHK001", PaymentIdentificationLines: [14]string{ "Invoice Date: 01/15/2024", "Invoice Number: 12345", "Purchase Order: 67890", "Description: Office Supplies", "", "Item Details:", " Paper - 10 reams @ $50.00 = $500.00", " Toner - 5 units @ $100.00 = $500.00", " Misc Supplies = $500.00", "", "Subtotal: $1,500.00", "Tax: $0.00", "Total: $1,500.00", "", }, }, }). Build() if err != nil { log.Fatal(err) } // Write to file output, err := os.Create("check_payments.spr") if err != nil { log.Fatal(err) } defer output.Close() writer := pamspr.NewWriter(output) if err := writer.Write(file); err != nil { log.Fatal(err) } fmt.Println("Check payment file created successfully!") }
The library includes comprehensive validation capabilities:
package main import ( "fmt" "log" "os" "github.com/moov-io/pamspr/pkg/pamspr" ) func main() { // Read file file, err := os.Open("payments.spr") if err != nil { log.Fatal(err) } defer file.Close() reader := pamspr.NewReader(file) pamFile, err := reader.Read() if err != nil { log.Fatal(err) } // Create validator validator := pamspr.NewValidator() // Validate file structure if err := validator.ValidateFileStructure(pamFile); err != nil { log.Printf("Structure validation failed: %v", err) } // Validate balancing (totals match) if err := validator.ValidateBalancing(pamFile); err != nil { log.Printf("Balancing validation failed: %v", err) } // Validate Same Day ACH requirements if err := validator.ValidateSameDayACH(pamFile); err != nil { log.Printf("Same Day ACH validation failed: %v", err) } // Validate individual payments for _, schedule := range pamFile.Schedules { for _, payment := range schedule.GetPayments() { if achPayment, ok := payment.(*pamspr.ACHPayment); ok { if err := validator.ValidateACHPayment(achPayment); err != nil { log.Printf("ACH payment validation failed: %v", err) } } if checkPayment, ok := payment.(*pamspr.CheckPayment); ok { if err := validator.ValidateCheckPayment(checkPayment); err != nil { log.Printf("Check payment validation failed: %v", err) } } } } fmt.Println("Validation completed!") }
The included command-line tool provides easy file operations:
pamspr -validate -input payments.spr
pamspr -info -input payments.spr
# Create sample ACH file pamspr -create ach -output sample_ach.spr # Create sample Check file pamspr -create check -output sample_check.sprConvert to JSON (Future Feature)
pamspr -convert -input payments.spr -output payments.jsonPerformance & Memory Management
The PAM SPR library is designed to handle files of any size efficiently through streaming processing. All Reader
and Writer
instances use streaming algorithms that maintain constant memory usage regardless of file size.
The library uses a streaming approach by default, providing:
// All readers and writers are streaming by default reader := pamspr.NewReader(file) // Memory-efficient streaming writer := pamspr.NewWriter(output) // Memory-efficient streamingPerformance Characteristics File Size Memory Usage Processing Speed Notes 1MB ~64KB High Fast startup 100MB ~64KB High Constant memory 1GB ~64KB High No memory growth 10GB+ ~64KB High Linear scaling
The reader provides three optimized processing modes:
1. Payment-Only Processing (Fastest)For applications that only need payment data:
reader := pamspr.NewReader(file) err := reader.ProcessPaymentsOnly(func(payment pamspr.Payment, scheduleIndex, paymentIndex int) bool { // Process each payment as it's read fmt.Printf("Payment %s: $%.2f\n", payment.GetPaymentID(), float64(payment.GetAmount())/100) // Return false to stop processing early return true })
Use Case: Payment processing, reporting, data extraction Memory Usage: Constant ~64KB regardless of file size
Performance: Fastest option - skips schedule object creation
For applications needing complete file structure:
reader := pamspr.NewReader(file) err := reader.ProcessFile( // Schedule callback func(schedule pamspr.Schedule, scheduleIndex int) bool { fmt.Printf("Processing schedule: %s\n", schedule.GetScheduleNumber()) return true }, // Payment callback func(payment pamspr.Payment, scheduleIndex, paymentIndex int) bool { // Process payment within context of its schedule return true }, // Optional record callback for debugging func(recordType string, lineNumber int, line string) { // Monitor all records as they're processed }, )
Use Case: Full file processing, validation, transformation Memory Usage: Constant ~64KB + callback data Performance: Full control with memory efficiency
3. Structure Validation Only (Ultra-Fast)For quick file validation without object creation:
reader := pamspr.NewReader(file) err := reader.ValidateFileStructureOnly() if err != nil { log.Printf("File structure invalid: %v", err) } stats := reader.GetStats() fmt.Printf("Validated %d lines, %d payments in %v\n", stats.LinesProcessed, stats.PaymentsProcessed, elapsed)
Use Case: File validation, integrity checking, format verification Memory Usage: Minimal ~8KB
Performance: Ultra-fast - no object allocation
For applications that need the complete file structure in memory:
// Traditional API (uses streaming internally) reader := pamspr.NewReader(file) pamFile, err := reader.Read() // Uses ReadAll() internally
Note: This builds the complete file structure in memory but uses streaming parsing internally for optimal performance.
For optimal performance with different file sizes and systems, configure buffer sizes:
Standard Configuration (Default)// Default settings - optimal for most use cases reader := pamspr.NewReader(file) writer := pamspr.NewWriter(output)
For files > 1GB or high-throughput processing:
config := &pamspr.ReaderConfig{ BufferSize: 256 * 1024, // 256KB buffer EnableValidation: true, // Keep validation enabled CollectErrors: false, // Disable error collection for speed SkipInvalidRecords: false, // Fail fast on errors } reader := pamspr.NewReaderWithConfig(file, config) writerConfig := &pamspr.WriterConfig{ BufferSize: 256 * 1024, // 256KB buffer FlushInterval: 1000, // Flush every 1000 records } writer := pamspr.NewWriterWithConfig(output, writerConfig)Memory-Constrained Configuration
For embedded systems or containers with limited memory:
config := &pamspr.ReaderConfig{ BufferSize: 16 * 1024, // 16KB buffer MaxErrors: 100, // Limit error collection CollectErrors: true, // Still collect some errors } reader := pamspr.NewReaderWithConfig(file, config)High-Throughput Configuration
For maximum processing speed:
config := &pamspr.ReaderConfig{ BufferSize: 512 * 1024, // 512KB buffer EnableValidation: false, // Skip validation for speed CollectErrors: false, // No error collection SkipInvalidRecords: true, // Continue on errors } reader := pamspr.NewReaderWithConfig(file, config)
Track processing performance with built-in statistics:
reader := pamspr.NewReader(file) // Process file... err := reader.ProcessPaymentsOnly(paymentCallback) // Get statistics stats := reader.GetStats() fmt.Printf(`Processing Statistics: Lines Processed: %d Payments Processed: %d Schedules Processed: %d Errors Encountered: %d Bytes Processed: %d `, stats.LinesProcessed, stats.PaymentsProcessed, stats.SchedulesProcessed, stats.ErrorsEncountered, stats.BytesProcessed)
runtime.ReadMemStats()
reader := pamspr.NewReaderWithConfig(file, &pamspr.ReaderConfig{ BufferSize: 1024 * 1024, // 1MB buffer for large files EnableValidation: true, CollectErrors: false, // Don't collect errors for memory efficiency MaxErrors: 0, SkipInvalidRecords: false, // Fail fast on corruption }) processed := 0 start := time.Now() err := reader.ProcessPaymentsOnly(func(payment pamspr.Payment, scheduleIndex, paymentIndex int) bool { // Process payment (e.g., send to database, queue, etc.) processPayment(payment) processed++ if processed%10000 == 0 { elapsed := time.Since(start) rate := float64(processed) / elapsed.Seconds() fmt.Printf("Processed %d payments (%.0f payments/sec)\n", processed, rate) } return true // Continue processing }) fmt.Printf("Total processed: %d payments in %v\n", processed, time.Since(start))Optimizing for Large Files
Traditional approach (loads entire file):
reader := pamspr.NewReader(file) pamFile, err := reader.Read() // Loads complete file structure if err != nil { return err } for _, schedule := range pamFile.Schedules { for _, payment := range schedule.GetPayments() { processPayment(payment) } }
Optimized approach (streaming processing):
reader := pamspr.NewReader(file) // Same constructor err := reader.ProcessPaymentsOnly(func(payment pamspr.Payment, scheduleIndex, paymentIndex int) bool { processPayment(payment) return true })
Benefits of optimization:
The complete PAM SPR file format specification is available in the docs/
directory, organized into easily-navigable sections:
The specification includes detailed field definitions, validation rules, error codes, and agency-specific requirements extracted from the official Treasury documentation.
File Format SpecificationsPAM SPR files use a hierarchical structure with fixed-width records:
File Header (H)
├── Schedule Header (01=ACH, 11=Check)
│ ├── Payment Record (02=ACH, 12=Check)
│ │ ├── Addendum Records (03, 04) [Optional]
│ │ ├── CARS TAS/BETC Records (G) [Optional]
│ │ ├── Check Stub (13) [Check Only]
│ │ └── DNP Record (DD) [Optional]
│ └── Schedule Trailer (T)
└── File Trailer (E)
The library includes specialized validation for all federal agencies with complete implementation:
✅ All Agencies Fully Implemented (100% Coverage) IRS (Internal Revenue Service) - FULLY IMPLEMENTEDThe library fully supports Same Day ACH processing with validation for:
The library provides helpful utilities for common operations:
// Pad left with zeros padded := pamspr.PadLeft("123", 5, '0') // "00123" // Pad right with spaces padded = pamspr.PadRight("ABC", 7, ' ') // "ABC " // Extract and pad numeric values numeric := pamspr.PadNumeric("ABC123DEF", 5) // "00123"
// Convert cents to dollar string dollars := pamspr.FormatCents(12345) // "123.45" // Parse dollar string to cents cents, err := pamspr.ParseAmount("$1,234.56") // 123456, nil
// Clean problematic characters clean := pamspr.CleanAddress(`123 "Main" & <Company>`) // `123 'Main' + (Company)`
// Format SSN ssn := pamspr.FormatTIN("123456789", "1") // "123-45-6789" // Format EIN ein := pamspr.FormatTIN("123456789", "2") // "12-3456789"
Run the comprehensive test suite:
# Run all tests go test ./... # Run tests with coverage go test -cover ./... # Run specific package tests go test ./pkg/pamspr/ # Run specific test go test -run TestReaderParseFile ./pkg/pamspr/ # Generate coverage report go test -coverprofile=coverage.out ./... go tool cover -html=coverage.out
See the examples/
directory for complete working examples:
# Run example code go run examples/examples.go
The examples demonstrate:
We welcome contributions! Please see our contributing guidelines for details.
# Clone the repository git clone https://github.com/moov-io/pamspr.git cd pamspr # Run tests go test ./... # Build CLI tool go build -o pamspr cmd/pamspr/main.go # Format code go fmt ./... # Run linter golangci-lint runContributing & Making the Library More Robust
We welcome contributions to improve the PAM SPR library! Here are critical areas where community help is needed to make this library production-ready for federal agencies:
🚨 Critical Needs for Production Readiness 1. Real Treasury Test Data & Validation RulesCurrent Gap: The library uses synthetic test data and lacks real Treasury-approved validation specifications.
What We Need:
Impact: Without real Treasury data, federal agencies cannot use this library in production as it may accept payments that Treasury would reject.
2. Enhanced Business Rule ValidationCurrent Status: 100% format validation coverage, business rules need agency input
Remaining Work:
How to Help: If you work at or with these agencies, we need SMEs to provide business validation requirements.
3. JSON/XML Export ImplementationCurrent Gap: CLI has -convert
flag but it's not implemented
What's Needed:
Current Gap: Limited to synthetic test files
What's Needed:
testdata/treasury/
For Agency SMEs: Help us get real validation requirements
# Contact these Treasury resources: # PAM.SAT@fiscal.treasury.gov - Test data access # FS.AgencyOutreach@fiscal.treasury.gov - Business rules
For Developers: Implement enhanced features
git clone https://github.com/moov-io/pamspr.git cd pamspr # Focus areas for enhancement: # - JSON/XML export in cmd/pamspr/main.go # - Business rule validation in pkg/pamspr/validator.go # - Performance optimization for large files
For Treasury Integration: Help obtain real test files
# We need files in testdata/treasury/ structure: # - testdata/treasury/valid/ach/ # - testdata/treasury/valid/check/ # - testdata/treasury/agency/{IRS,VA,SSA,RRB,CCC}/ # - testdata/treasury/invalid/ (for error testing)
The Goal: Make this library production-ready so federal agencies can confidently process payments without risk of Treasury rejection.
This project is licensed under the Apache License 2.0 - see the LICENSE file for details.
Disclaimer: This library is not affiliated with the U.S. Treasury or any federal agency. It is an open-source implementation of the PAM SPR file format for integration purposes.
RetroSearch is an open source project built by @garambo | Open a GitHub Issue
Search and Browse the WWW like it's 1997 | Search results from DuckDuckGo
HTML:
3.2
| Encoding:
UTF-8
| Version:
0.7.4