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:
- Salaries and Benefits: Federal employee payroll, retirement, and benefit payments
- Vendor Payments: Payments to contractors and service providers
- Tax Refunds: Individual and business tax refund processing
- Social Security: SSA benefit payments and Medicare reimbursements
- Other Federal Payments: Education loans, agricultural subsidies, and more
The library supports both ACH (Automated Clearing House) and Check payment methods, with comprehensive validation for Same Day ACH requirements and agency-specific rules.
- ✅ Read PAM SPR Files: Parse fixed-width 850-character records
- ✅ Write PAM SPR Files: Generate compliant payment files with proper formatting
- ✅ Validate Files: Comprehensive validation including balancing and business rules
- ✅ ACH Support: CCD, PPD, IAT, and CTX Standard Entry Class codes
- ✅ Check Support: Paper check generation with customizable stubs
- ✅ Same Day ACH: Validation for expedited ACH processing requirements
- ✅ Agency-Specific Validation: Complete validation for all federal agencies (IRS, VA, SSA, RRB, CCC)
- ✅ File Builder: Fluent API for programmatic file construction
- ✅ Utility Functions: Field formatting, amount conversion, address cleaning
- ✅ CLI Tool: Command-line interface for file operations
- ✅ Comprehensive Testing: Extensive test coverage with 250+ test cases
- ✅ Streaming Processing: Memory-efficient handling of gigabyte-sized files
- ✅ Constant Memory Usage: Process any file size with predictable memory footprint
- ✅ Configurable Buffering: Tunable buffer sizes for optimal throughput
- ✅ Payment-Only Mode: Fast processing when full file structure isn't needed
- ✅ Structure Validation: Quick file validation without object creation
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)
}
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!")
}
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.spr
pamspr -convert -input payments.spr -output payments.json
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:
- Constant Memory Usage: Process any file size with predictable memory footprint (~64KB)
- Fast Processing: Optimized for high-throughput scenarios
- Scalable: Handle gigabyte-sized files without running out of memory
// All readers and writers are streaming by default
reader := pamspr.NewReader(file) // Memory-efficient streaming
writer := pamspr.NewWriter(output) // Memory-efficient streaming
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:
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
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:
// 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)
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)
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)
- Use Payment-Only Mode when you don't need full file structure
- Avoid ReadAll() for large files - use callbacks instead
- Configure appropriate buffer sizes based on your system resources
- Disable error collection for maximum performance in trusted environments
- Process in chunks using early termination in callbacks
- Use goroutines for parallel processing of payment data
- Monitor memory usage with
runtime.ReadMemStats()
- Implement backpressure if downstream processing is slower
- Set MaxErrors to prevent memory growth from error collection
- Use SkipInvalidRecords for fault-tolerant processing
- Log errors asynchronously to avoid blocking I/O
- Implement circuit breakers for downstream service failures
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))
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:
- 10x-100x less memory usage for large files
- 2x-10x faster processing depending on file size
- Handles any file size without running out of memory
- Early termination support for finding specific payments
- Better error handling with configurable fault tolerance
The complete PAM SPR file format specification is available in the docs/
directory, organized into easily-navigable sections:
- docs/README.md - Complete specification index and navigation guide
- Core Specifications: 01-general-instructions.md through 09-specification-notes.md
- Record Specifications: record-01-file-header.md through record-12-file-trailer.md
- Reference Data: appendix-a-ach-transaction-codes.md through appendix-e-paymenttypecode-values.md
The specification includes detailed field definitions, validation rules, error codes, and agency-specific requirements extracted from the official Treasury documentation.
PAM 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)
- H: File Header - Contains system information and processing flags
- 01: ACH Schedule Header - ACH-specific schedule information
- 11: Check Schedule Header - Check-specific schedule information
- 02: ACH Payment Record - Individual ACH payment details
- 12: Check Payment Record - Individual check payment details
- 03: ACH Addendum - Additional payment information (80 chars)
- 04: CTX Addendum - Corporate Trade Exchange data (800 chars)
- 13: Check Stub - Check stub information with 14 lines of detail
- G: CARS TAS/BETC - Government accounting classification
- DD: DNP Record - Do Not Pay verification information
- T: Schedule Trailer - Schedule totals and counts
- E: File Trailer - File totals and counts
- Fixed Length: All records are exactly 850 characters
- Balancing Required: File and schedule totals must match payment sums
- Sequential Processing: Records must appear in hierarchical order
- Amount Format: All amounts stored as cents (integers)
The library includes specialized validation for all federal agencies with complete implementation:
- ✅ Reconcilement field length validation (100 characters)
- ✅ Standard and savings bond format parsing
- ✅ Tax period, MFT code, and service center validation
⚠️ Business Rules: Valid code ranges require Treasury input
- ✅ Station code and FIN code validation (required fields)
- ✅ Reconcilement field parsing and validation (100 characters)
- ✅ ACH vs Check payment type handling
- ✅ Courtesy code validation for check payments
- ✅ Policy number and appropriation code validation
- ✅ Comprehensive error handling with specific validation rules
- ✅ Program service center code validation (1 character, required)
- ✅ Payment ID code validation (2 characters, required)
- ✅ TIN indicator offset validation (with SSA-A variant support)
- ✅ Support for SSA, SSA-Daily, and SSA-A processing variants
- ✅ Reconcilement field parsing and business rule enforcement
- ✅ Beneficiary Symbol validation (2-character alphanumeric field)
- ✅ Prefix Code validation (1-character alphanumeric field)
- ✅ Payee Code validation (1-character alphanumeric field)
- ✅ Object Code validation (1-character alphanumeric field for PACER integration)
- ✅ Complete reconcilement field parsing and validation
⚠️ Business Rules: Valid code ranges require agency input
- ✅ TOP Payment Agency ID validation (2-character alphabetic field)
- ✅ TOP Agency Site ID validation (2-character alphabetic field)
- ✅ Alphabetic-only character validation for TOP fields
- ✅ Support for optional TOP fields (empty fields are valid)
- ✅ Schedule-level TOP ID inheritance business rule support
⚠️ Business Rules: Valid program codes require agency input
- 100% Format Validation: All agencies have complete field format validation
- Pending Business Rules: Valid code ranges and payment limits require agency/Treasury input
- Production Ready: All format validation requirements satisfied per Treasury specification
The library fully supports Same Day ACH processing with validation for:
- Amount Limits: $1,000,000 per transaction limit enforcement
- Time Windows: Processing deadline validation
- SEC Code Restrictions: Supported Standard Entry Class codes
- Settlement Requirements: Same-day settlement validation
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:
- Creating ACH payment files with addenda and CARS records
- Creating check payment files with stubs
- Parsing and validating existing files
- Same Day ACH validation scenarios
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 run
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:
Current Gap: The library uses synthetic test data and lacks real Treasury-approved validation specifications.
What We Need:
- Contact PAM.SAT@fiscal.treasury.gov to obtain real test files
- Request agency-specific validation business rules from Treasury
- Valid code ranges for each agency (station codes, FIN codes, PSC codes, etc.)
- Real edge cases and error scenarios from Treasury systems
Impact: Without real Treasury data, federal agencies cannot use this library in production as it may accept payments that Treasury would reject.
Current Status: 100% format validation coverage, business rules need agency input
Remaining Work:
- All Agencies: Valid code ranges (station codes, FIN codes, PSC codes, etc.)
- All Agencies: Payment amount limits, cross-field validation, type restrictions
- Agency-Specific: Business rule validation beyond format requirements
- Treasury Integration: Real validation rules from Treasury systems
How to Help: If you work at or with these agencies, we need SMEs to provide business validation requirements.
Current Gap: CLI has -convert
flag but it's not implemented
What's Needed:
- JSON schema design that preserves all SPR data
- Marshaling/unmarshaling for all record types
- Roundtrip validation (SPR → JSON → SPR)
- XML support for legacy system integration
Current Gap: Limited to synthetic test files
What's Needed:
- Treasury-approved test files in
testdata/treasury/
- Invalid file examples that should fail validation
- Large file performance testing
- Edge cases with maximum field lengths
Area | Priority | Impact | Effort |
---|---|---|---|
Real Treasury test files | CRITICAL | High | Contact Treasury |
Business rule validation | High | Medium | Agency coordination |
JSON export implementation | Medium | High | 1 week |
Performance optimization | Low | Medium | 2 weeks |
Streaming support | Low | Medium | 2 weeks |
-
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)
- REFACTORING.md - Detailed implementation roadmap
- testdata/synthetic/README.md - Test data requirements
- validator_agency_test.go - Validation test patterns
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.
- Documentation: See inline code documentation and examples
- Issues: Report bugs or request features via GitHub Issues
- Community: Join the Moov Community for discussions
- moov-io/ach - ACH file processing library
- moov-io/fed - Federal Reserve routing number lookup
- moov-io/paygate - Payment gateway service
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.