A lightweight Ruby client for JDPI microservices (Auth, DICT, QR, SPI OP/OD, Participants). No Rails dependency. All service base URLs are fully configurable by environment.
Before installing jdpi_client, ensure you have:
- Ruby 3.0 or higher (tested on Ruby 3.0, 3.1, 3.2, 3.3, and 3.4)
- Bundler 2.0+ for dependency management
- System dependencies (for advanced token storage):
libpq-dev(for PostgreSQL storage backend)libsqlite3-dev(for SQLite storage backend)
ruby --version # Should be 3.0+
bundle --version # Should be 2.0+Ubuntu/Debian:
sudo apt-get update
sudo apt-get install -y libpq-dev libsqlite3-devmacOS (Homebrew):
brew install postgresql sqlite3Add this line to your application's Gemfile:
gem 'jdpi_client', git: 'https://github.com/agramms/jdpi-client.git'Then execute:
bundle installClone and install from source:
git clone https://github.com/agramms/jdpi-client.git
cd jdpi-client
bundle install
gem build jdpi_client.gemspec
gem install jdpi_client-0.1.0.gemFor a containerized development environment:
git clone https://github.com/agramms/jdpi-client.git
cd jdpi-client
docker-compose up -d # Starts Redis, PostgreSQL, and DynamoDB Local
bundle installFollow these steps to get jdpi_client up and running:
- Install prerequisites (Ruby 3.0+, Bundler 2.0+, system dependencies)
- Install the gem using one of the methods above
- Create environment file (
.envfor local development)
Create a configuration file or add to your application initializer:
# config/initializers/jdpi_client.rb (Rails)
# or create a separate configuration file
require 'jdpi_client'
JDPIClient.configure do |config|
# Required settings
config.jdpi_client_host = ENV.fetch('JDPI_CLIENT_HOST')
config.oauth_client_id = ENV.fetch('JDPI_CLIENT_ID')
config.oauth_secret = ENV.fetch('JDPI_CLIENT_SECRET')
# Optional settings
config.timeout = 10
config.logger = Logger.new($stdout) # Enable logging
endCreate your .env file or set these environment variables:
# Required
JDPI_CLIENT_HOST=api.yourbank.homl.jdpi.pstijd
JDPI_CLIENT_ID=your_oauth_client_id
JDPI_CLIENT_SECRET=your_oauth_secret
# Optional (for advanced features)
JDPI_TOKEN_ENCRYPTION_KEY=your_32_character_encryption_key_here
REDIS_URL=redis://localhost:6379/0
DATABASE_URL=postgresql://user:password@localhost:5432/your_appTest your setup with this simple script:
require 'jdpi_client'
# Test configuration
puts "✅ Gem loaded successfully"
puts "🌍 Environment: #{JDPIClient.config&.environment || 'not configured'}"
puts "🔗 Host: #{JDPIClient.config&.jdpi_client_host || 'not configured'}"
# Test authentication (requires valid credentials)
begin
auth_client = JDPIClient::Auth::Client.new
token = auth_client.token!
puts "🔑 Authentication: SUCCESS"
rescue => e
puts "❌ Authentication failed: #{e.message}"
endTry your first JDPI API call:
# Get participants list (simple read operation)
begin
participants = JDPIClient::Participants.new
result = participants.list
puts "📋 Participants API: SUCCESS (#{result.size} participants)"
rescue => e
puts "❌ API call failed: #{e.message}"
endrequire 'jdpi_client'
JDPIClient.configure do |c|
c.jdpi_client_host = ENV.fetch('JDPI_CLIENT_HOST', 'api.mybank.homl.jdpi.pstijd')
# Environment and protocol auto-detected from hostname:
# - Contains 'prod' or 'production' -> HTTPS + production
# - Otherwise -> HTTP + homolog
c.oauth_client_id = ENV['JDPI_CLIENT_ID']
c.oauth_secret = ENV['JDPI_CLIENT_SECRET']
c.timeout = 8
c.open_timeout = 2
c.logger = Logger.new($stdout)
end
token = JDPIClient::Auth::Client.new.token!
resp = JDPIClient::SPI::OP.new.create_order!(
valor: 1050,
chave: "fulano@bank.com",
prioridade_pagamento: 0,
finalidade: 1,
dt_hr_requisicao_psp: Time.now.utc.iso8601
)- 🔐 Automatic OAuth2 token management with thread-safe caching
- 🏢 Multi-backend token storage (Memory, Redis, Database, DynamoDB)
- 🔒 Token encryption for sensitive data protection
- 🌐 Environment auto-detection from hostname (prod/homl)
- 🔄 Built-in retry logic with exponential backoff
- 🛡️ Comprehensive error handling with structured exceptions
- 🔑 Idempotency support for safe payment operations
- ⚡ Distributed locking for clustered environments
- 📝 Request/response logging for debugging
- 🚀 No Rails dependency - works with any Ruby application
- 🧪 High test coverage (75.65%) with comprehensive test suite
- Auth - OAuth2 authentication and token management
- SPI OP - Payment initiation and settlement
- SPI OD - Refund and dispute management
- DICT - PIX key management, claims, and infractions
- QR - QR code generation for payments
- Participants - Participant information management
The gem automatically detects environment and protocol from the hostname:
| Hostname Pattern | Environment | Protocol | Example |
|---|---|---|---|
Contains prod or production |
Production | HTTPS | https://api.bank.prod.jdpi.pstijd |
| Any other hostname | Homolog | HTTP | http://api.bank.homl.jdpi.pstijd |
JDPIClient.configure do |config|
config.jdpi_client_host = ENV.fetch('JDPI_CLIENT_HOST')
config.oauth_client_id = ENV.fetch('JDPI_CLIENT_ID')
config.oauth_secret = ENV.fetch('JDPI_CLIENT_SECRET')
# Optional settings
config.timeout = 10 # Request timeout in seconds
config.open_timeout = 3 # Connection timeout in seconds
config.logger = Rails.logger if defined?(Rails)
# Token storage configuration (for clustered environments)
config.token_storage_adapter = :memory # Default: in-memory storage
config.token_storage_key_prefix = 'jdpi_client' # Cache key prefix
config.token_encryption_enabled = true # Encrypt sensitive tokens
config.token_encryption_key = ENV['JDPI_TOKEN_ENCRYPTION_KEY'] # 32+ character key
endThe gem supports multiple token storage backends for clustered and distributed environments:
config.token_storage_adapter = :memory
# ✅ Fast and simple
# ❌ Not shared between processes/servers
# 👍 Best for: Single-server applications, developmentconfig.token_storage_adapter = :redis
config.token_storage_url = ENV['REDIS_URL'] || 'redis://localhost:6379/0'
config.token_storage_options = {
timeout: 5,
reconnect_attempts: 3,
reconnect_delay: 1
}
# ✅ Distributed caching with automatic expiration
# ✅ High performance with built-in clustering
# 👍 Best for: Production clustered environmentsconfig.token_storage_adapter = :database
config.token_storage_url = ENV['DATABASE_URL'] # Any database supported by Ruby
config.token_storage_options = {
table_name: 'jdpi_client_tokens' # Custom table name
}
# ✅ Persistent storage with transaction safety
# ✅ Works with existing database infrastructure
# 👍 Best for: Applications with existing database setupconfig.token_storage_adapter = :dynamodb
config.token_storage_options = {
table_name: 'jdpi-tokens',
region: 'us-east-1',
access_key_id: ENV['AWS_ACCESS_KEY_ID'], # Optional if using IAM roles
secret_access_key: ENV['AWS_SECRET_ACCESS_KEY'] # Optional if using IAM roles
}
# ✅ Serverless with automatic scaling
# ✅ Built-in TTL for automatic token cleanup
# 👍 Best for: AWS-based serverless applicationsEnable encryption for sensitive token data:
config.token_encryption_enabled = true
config.token_encryption_key = ENV['JDPI_TOKEN_ENCRYPTION_KEY'] # 32+ characters required
# Generate a secure encryption key:
# ruby -e "require 'securerandom'; puts SecureRandom.hex(32)"Rails Application with Redis:
# config/initializers/jdpi_client.rb
JDPIClient.configure do |config|
config.jdpi_client_host = ENV.fetch('JDPI_CLIENT_HOST')
config.oauth_client_id = ENV.fetch('JDPI_CLIENT_ID')
config.oauth_secret = ENV.fetch('JDPI_CLIENT_SECRET')
config.logger = Rails.logger
# Shared Redis cache for clustered Rails servers
config.token_storage_adapter = :redis
config.token_storage_url = ENV.fetch('REDIS_URL')
config.token_encryption_enabled = Rails.env.production?
config.token_encryption_key = ENV.fetch('JDPI_TOKEN_ENCRYPTION_KEY')
config.token_storage_key_prefix = "#{Rails.env}:jdpi"
endServerless Application with DynamoDB:
JDPIClient.configure do |config|
config.jdpi_client_host = ENV.fetch('JDPI_CLIENT_HOST')
config.oauth_client_id = ENV.fetch('JDPI_CLIENT_ID')
config.oauth_secret = ENV.fetch('JDPI_CLIENT_SECRET')
# DynamoDB for serverless environments
config.token_storage_adapter = :dynamodb
config.token_storage_options = {
table_name: ENV.fetch('JDPI_TOKENS_TABLE', 'jdpi-tokens'),
region: ENV.fetch('AWS_REGION', 'us-east-1')
}
config.token_encryption_enabled = true
config.token_encryption_key = ENV.fetch('JDPI_TOKEN_ENCRYPTION_KEY')
endDevelopment/Testing:
JDPIClient.configure do |config|
config.jdpi_client_host = 'api.test.homl.jdpi.pstijd'
config.oauth_client_id = 'test_client_id'
config.oauth_secret = 'test_secret'
# Simple memory storage for development
config.token_storage_adapter = :memory
config.logger = Logger.new($stdout) if ENV['DEBUG']
endThis section lists all environment variables that jdpi_client recognizes:
| Variable | Description | Example | Notes |
|---|---|---|---|
JDPI_CLIENT_HOST |
JDPI API hostname | api.mybank.homl.jdpi.pstijd |
Auto-detects environment and protocol |
JDPI_CLIENT_ID |
OAuth2 client ID | your_client_id |
Provided by JDPI |
JDPI_CLIENT_SECRET |
OAuth2 client secret | your_client_secret |
Keep secure! |
| Variable | Description | Default | Example |
|---|---|---|---|
JDPI_TIMEOUT |
Request timeout (seconds) | 8 |
10 |
JDPI_OPEN_TIMEOUT |
Connection timeout (seconds) | 2 |
3 |
JDPI_LOG_LEVEL |
Logging level | info |
debug, warn, error |
JDPI_TOKEN_ENCRYPTION_KEY |
Token encryption key (32+ chars) | nil |
Generated with SecureRandom.hex(32) |
| Variable | Description | Default | Example |
|---|---|---|---|
REDIS_URL |
Redis connection URL | redis://localhost:6379/0 |
redis://user:pass@host:port/db |
JDPI_REDIS_TIMEOUT |
Redis operation timeout | 5 |
10 |
JDPI_REDIS_RECONNECT_ATTEMPTS |
Reconnection attempts | 3 |
5 |
| Variable | Description | Default | Example |
|---|---|---|---|
DATABASE_URL |
Database connection URL | nil |
postgresql://user:pass@host/db |
JDPI_DB_TABLE_NAME |
Token storage table name | jdpi_client_tokens |
my_tokens |
| Variable | Description | Default | Example |
|---|---|---|---|
AWS_REGION |
AWS region | us-east-1 |
us-west-2 |
AWS_ACCESS_KEY_ID |
AWS access key | nil |
IAM user access key |
AWS_SECRET_ACCESS_KEY |
AWS secret key | nil |
IAM user secret |
JDPI_DYNAMODB_TABLE |
DynamoDB table name | jdpi-tokens |
my-jdpi-tokens |
DYNAMODB_ENDPOINT |
DynamoDB endpoint (for local) | nil |
http://localhost:8000 |
| Variable | Description | Default | Example |
|---|---|---|---|
RAILS_ENV / RACK_ENV |
Application environment | nil |
development, production |
DEBUG |
Enable debug logging | false |
true |
COVERAGE |
Enable test coverage | false |
true |
TEST_ADAPTER |
Test storage adapter | memory |
redis, database, dynamodb |
Development (.env.development):
JDPI_CLIENT_HOST=api.test.homl.jdpi.pstijd
JDPI_CLIENT_ID=test_client_id
JDPI_CLIENT_SECRET=test_client_secret
JDPI_TIMEOUT=10
DEBUG=trueProduction (.env.production):
JDPI_CLIENT_HOST=api.mybank.prod.jdpi.pstijd
JDPI_CLIENT_ID=prod_client_id
JDPI_CLIENT_SECRET=super_secure_secret
JDPI_TOKEN_ENCRYPTION_KEY=1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef
REDIS_URL=redis://cache.production.com:6379/1
JDPI_TIMEOUT=8
JDPI_OPEN_TIMEOUT=3Testing (.env.test):
JDPI_CLIENT_HOST=api.test.homl.jdpi.pstijd
JDPI_CLIENT_ID=test_client
JDPI_CLIENT_SECRET=test_secret
TEST_ADAPTER=memory
COVERAGE=trueThe gem automatically handles OAuth2 token management with intelligent caching:
# Basic token usage - automatically cached and refreshed
auth_client = JDPIClient::Auth::Client.new
token = auth_client.token! # Thread-safe, auto-refreshes when expired
# Scope-specific tokens for different JDPI services
dict_token = auth_client.token!(requested_scopes: ['auth_apim', 'dict_api'])
spi_token = auth_client.token!(requested_scopes: ['auth_apim', 'spi_api'])
# Use as a proc for HTTP clients
token_provider = auth_client.to_proc
http_client.authorization = token_provider
# Force token refresh if needed
auth_client.refresh!
# Get token information for debugging
info = auth_client.token_info
puts "Token cached: #{info[:cached]}"
puts "Storage type: #{info[:storage_type]}"
puts "Expires at: #{info[:expires_at]}"# Create payment order
spi_client = JDPIClient::SPI::OP.new
response = spi_client.create_order!(
valor: 2500, # R$ 25.00 in centavos
chave: "user@bank.com",
descricao: "Payment for services",
prioridade_pagamento: 0,
finalidade: 1,
dt_hr_requisicao_psp: Time.now.utc.iso8601,
idempotency_key: SecureRandom.uuid
)
# Check payment status
status = spi_client.consult_request(response['id_req'])# Register new PIX key
dict_client = JDPIClient::DICT::Keys.new
dict_client.create_key!(
tipo: "EMAIL",
chave: "user@bank.com",
conta: account_info
)
# Claim existing key
claims_client = JDPIClient::DICT::Claims.new
claims_client.create_claim!(
chave: "user@bank.com",
tipo_reivindicacao: "OWNERSHIP"
)# Generate payment QR code
qr_client = JDPIClient::QR::Client.new
qr_response = qr_client.create_qr!(
valor: 1500, # R$ 15.00
descricao: "Coffee payment"
)
puts qr_response['qr_code']
puts qr_response['pix_copia_cola']This section provides detailed information about JDPI APIs and their request/response formats.
For comprehensive API documentation, see the /docs directory:
- JDPI API Overview - Introduction to JDPI architecture
- Authentication - OAuth2 flow and token management
- SPI Operations - PIX payment APIs (OP/OD)
- DICT Services - PIX key management
- QR Code Generation - QR code creation and validation
- Participants API - Participant information
Endpoint: /auth/jdpi/connect/token
Request Format:
POST /auth/jdpi/connect/token
Content-Type: application/x-www-form-urlencoded
client_id=YOUR_CLIENT_ID&
client_secret=YOUR_CLIENT_SECRET&
grant_type=client_credentials&
scope=auth_apim spi_api dict_apiResponse Format:
{
"access_token": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...",
"token_type": "Bearer",
"expires_in": 3600,
"scope": "auth_apim spi_api dict_api"
}Ruby Example:
auth_client = JDPIClient::Auth::Client.new
token = auth_client.token!
# Token is automatically cached and refreshedEndpoint: /spi/api/v1/op
Request Format:
POST /spi/api/v1/op
Authorization: Bearer {token}
Content-Type: application/json
{
"valor": 1050,
"chave": "user@bank.com",
"descricao": "Payment description",
"prioridade_pagamento": 0,
"finalidade": 1,
"dt_hr_requisicao_psp": "2024-01-15T10:30:00Z",
"idempotency_key": "unique-request-id-123"
}Response Format:
{
"id_req": "12345678-1234-1234-1234-123456789012",
"status": "ACSP",
"dt_hr_resposta": "2024-01-15T10:30:01Z",
"valor": 1050,
"chave": "user@bank.com"
}Ruby Example:
spi_client = JDPIClient::SPI::OP.new
response = spi_client.create_order!(
valor: 1050, # R$ 10.50 in centavos
chave: "user@bank.com",
descricao: "Coffee payment",
prioridade_pagamento: 0,
finalidade: 1,
dt_hr_requisicao_psp: Time.now.utc.iso8601,
idempotency_key: SecureRandom.uuid
)
puts response['id_req'] # Payment IDEndpoint: /spi/api/v1/op/{id_req}
Request Format:
GET /spi/api/v1/op/12345678-1234-1234-1234-123456789012
Authorization: Bearer {token}Response Format:
{
"id_req": "12345678-1234-1234-1234-123456789012",
"status": "ACCC",
"dt_hr_resposta": "2024-01-15T10:30:01Z",
"valor": 1050,
"chave": "user@bank.com",
"end_to_end_id": "E12345678202401151030123456789012"
}Endpoint: /dict/api/v2/key
Request Format:
POST /dict/api/v2/key
Authorization: Bearer {token}
Content-Type: application/json
{
"tipo": "EMAIL",
"chave": "user@bank.com",
"conta": {
"ispb": "12345678",
"agencia": "0001",
"conta": "12345678",
"tipo_conta": "CACC"
}
}Response Format:
{
"chave": "user@bank.com",
"tipo": "EMAIL",
"status": "OWNED",
"dt_criacao": "2024-01-15T10:30:00Z"
}Ruby Example:
dict_client = JDPIClient::DICT::Keys.new
response = dict_client.create_key!(
tipo: "EMAIL",
chave: "user@bank.com",
conta: {
ispb: "12345678",
agencia: "0001",
conta: "12345678",
tipo_conta: "CACC"
}
)Endpoint: /dict/api/v2/key/{key_value}
Request Format:
GET /dict/api/v2/key/user@bank.com
Authorization: Bearer {token}Response Format:
{
"chave": "user@bank.com",
"tipo": "EMAIL",
"conta": {
"ispb": "12345678",
"nome": "Bank Name",
"agencia": "0001",
"conta": "12345678",
"tipo_conta": "CACC"
},
"status": "OWNED"
}Endpoint: /qr/api/v1/qr
Request Format:
POST /qr/api/v1/qr
Authorization: Bearer {token}
Content-Type: application/json
{
"valor": 2500,
"descricao": "Payment for services",
"chave": "merchant@bank.com"
}Response Format:
{
"qr_code": "iVBORw0KGgoAAAANSUhEUgAAASwAAAEsCAYAAAB5fY51...",
"pix_copia_cola": "00020126580014br.gov.bcb.pix013639c02...",
"txid": "12345678901234567890123456789012"
}Ruby Example:
qr_client = JDPIClient::QR::Client.new
response = qr_client.create_qr!(
valor: 2500, # R$ 25.00
descricao: "Coffee and pastry",
chave: "merchant@bank.com"
)
puts "QR Code: #{response['qr_code']}"
puts "PIX Copy/Paste: #{response['pix_copia_cola']}"| HTTP Status | Description | Action |
|---|---|---|
| 200 | Success | Request completed successfully |
| 201 | Created | Resource created successfully |
| 400 | Bad Request | Check request format and parameters |
| 401 | Unauthorized | Refresh authentication token |
| 403 | Forbidden | Check permissions and scope |
| 404 | Not Found | Resource doesn't exist |
| 429 | Rate Limited | Implement backoff/retry logic |
| 500 | Internal Error | Retry with exponential backoff |
| 502/503 | Service Unavailable | Check JDPI service status |
Common Request Headers:
Authorization: Bearer {access_token}
Content-Type: application/json
Accept: application/json
X-Idempotency-Key: {unique-request-id} # For payment operationsCommon Response Headers:
Content-Type: application/json
X-RateLimit-Limit: 100
X-RateLimit-Remaining: 95
X-RateLimit-Reset: 1640995200
X-Request-ID: 12345678-1234-1234-1234-123456789012All JDPI APIs return structured error responses:
{
"error": {
"code": "INVALID_REQUEST",
"message": "The request format is invalid",
"details": [
{
"field": "valor",
"message": "Value must be greater than 0"
}
]
},
"timestamp": "2024-01-15T10:30:00Z",
"path": "/spi/api/v1/op"
}JDPI can send webhooks for payment status updates:
Webhook Payload Format:
{
"event_type": "payment.status_changed",
"id_req": "12345678-1234-1234-1234-123456789012",
"status": "ACCC",
"timestamp": "2024-01-15T10:30:01Z",
"end_to_end_id": "E12345678202401151030123456789012"
}Webhook Signature Verification:
# Verify webhook authenticity
def verify_webhook(payload, signature, secret)
expected_signature = OpenSSL::HMAC.hexdigest('sha256', secret, payload)
Rack::Utils.secure_compare(signature, expected_signature)
endHomolog Environment:
- Base URL:
http://api.bank.homl.jdpi.pstijd - Use test credentials provided by JDPI
- Safe for testing without real money
Test PIX Keys:
- Email:
test@example.com - Phone:
+5511999999999 - CPF:
12345678901(test CPF) - Random Key:
123e4567-e89b-12d3-a456-426614174000
The gem provides structured error handling for different scenarios:
begin
response = spi_client.create_order!(payment_data)
rescue JDPIClient::Errors::Validation => e
# Handle validation errors (400)
puts "Invalid request: #{e.message}"
rescue JDPIClient::Errors::Unauthorized => e
# Handle authentication errors (401)
puts "Authentication failed: #{e.message}"
rescue JDPIClient::Errors::RateLimited => e
# Handle rate limiting (429)
puts "Rate limited, please retry later"
rescue JDPIClient::Errors::ServerError => e
# Handle server errors (5xx)
puts "Server error: #{e.message}"
endSecurity is critical when working with PIX payments. Follow these best practices:
✅ DO:
- Store credentials in environment variables, never in code
- Use encrypted storage for production credentials
- Rotate OAuth secrets regularly
- Use different credentials for each environment
❌ DON'T:
- Commit credentials to version control
- Log or print sensitive information
- Share credentials across environments
- Store credentials in plain text files
# ✅ Good - Environment variables
config.oauth_secret = ENV.fetch('JDPI_CLIENT_SECRET')
# ❌ Bad - Hard-coded credentials
config.oauth_secret = 'my-secret-key' # Never do this!Enable token encryption in production:
config.token_encryption_enabled = true
config.token_encryption_key = ENV.fetch('JDPI_TOKEN_ENCRYPTION_KEY')
# Generate a secure encryption key:
# ruby -e "require 'securerandom'; puts SecureRandom.hex(32)"Token storage recommendations:
- Use Redis/Database/DynamoDB in production (not memory)
- Enable encryption for all stored tokens
- Set appropriate token TTL values
- Monitor token usage patterns
HTTPS/TLS Configuration:
- Always use HTTPS in production (auto-detected for
prodhostnames) - Validate SSL certificates
- Use TLS 1.2 or higher
- Configure proper timeout values
config.timeout = 8 # Request timeout
config.open_timeout = 3 # Connection timeoutFirewall and Access:
- Whitelist JDPI IP addresses
- Restrict outbound connections to JDPI endpoints only
- Use VPN or private networks when possible
- Monitor network traffic for anomalies
Safe logging practices:
# ✅ Good - Structured logging without sensitive data
logger.info "PIX payment initiated", {
order_id: order_id,
amount_cents: amount,
timestamp: Time.now.utc.iso8601
}
# ❌ Bad - Logging sensitive information
logger.info "Token: #{token}" # Never log tokens!
logger.info "Request: #{request_body.inspect}" # May contain secretsConfigure logging levels:
# Production - minimal logging
config.logger.level = Logger::WARN
# Development - detailed logging
config.logger.level = Logger::DEBUG if Rails.env.development?Input validation:
- Validate all payment parameters
- Sanitize user inputs
- Use strong typing where possible
- Implement request size limits
Error handling:
- Never expose internal errors to users
- Log security events (failed authentication, etc.)
- Implement rate limiting
- Use structured error responses
begin
response = spi_client.create_order!(payment_data)
rescue JDPIClient::Errors::Unauthorized => e
# ✅ Good - Log security event, return generic error
security_logger.warn "Authentication failed for client #{client_id}"
render json: { error: 'Authentication failed' }, status: :unauthorized
# ❌ Bad - Expose detailed error information
render json: { error: e.message }, status: :unauthorized
endEnvironment separation:
- Use completely separate credentials for each environment
- Never use production credentials in development/testing
- Implement proper CI/CD security practices
- Use infrastructure as code for consistent deployments
Secret management:
- Use dedicated secret management services (AWS Secrets Manager, Azure Key Vault, etc.)
- Implement secret rotation procedures
- Monitor secret access and usage
- Use service accounts with minimal permissions
Example secure production configuration:
# config/initializers/jdpi_client.rb
JDPIClient.configure do |config|
config.jdpi_client_host = ENV.fetch('JDPI_CLIENT_HOST')
config.oauth_client_id = ENV.fetch('JDPI_CLIENT_ID')
config.oauth_secret = ENV.fetch('JDPI_CLIENT_SECRET')
# Security settings
config.timeout = 8
config.open_timeout = 3
config.logger = Rails.logger
config.logger.level = Logger::WARN # Minimal production logging
# Encrypted token storage
config.token_storage_adapter = :redis
config.token_storage_url = ENV.fetch('REDIS_URL')
config.token_encryption_enabled = true
config.token_encryption_key = ENV.fetch('JDPI_TOKEN_ENCRYPTION_KEY')
config.token_storage_key_prefix = "#{Rails.env}:jdpi"
endSet up monitoring for:
- Failed authentication attempts
- Unusual payment patterns
- Token refresh failures
- Network connectivity issues
- Performance degradation
Security alerts:
- Multiple authentication failures
- Unexpected geographic access
- Token encryption/decryption failures
- Suspicious payment amounts or patterns
PIX Compliance:
- Follow Central Bank of Brazil regulations
- Implement proper audit logs
- Maintain transaction records
- Follow data protection requirements (LGPD)
Audit logging:
- Log all payment operations with timestamps
- Track user actions and changes
- Maintain immutable audit trails
- Regular compliance reviews
Understanding JDPI's performance characteristics and rate limits is crucial for production applications.
Standard Rate Limits:
- Authentication: 10 requests/minute per client
- PIX Payments (SPI OP): 100 requests/minute per participant
- PIX Queries (SPI OD): 200 requests/minute per participant
- DICT Operations: 50 requests/minute per participant
- QR Generation: 300 requests/minute per participant
Rate Limit Headers: JDPI returns these headers with rate limit information:
X-RateLimit-Limit: 100
X-RateLimit-Remaining: 45
X-RateLimit-Reset: 1640995200
Expected Response Times:
- Token Requests: < 200ms
- PIX Payments: < 500ms
- Balance Queries: < 300ms
- DICT Lookups: < 400ms
- QR Generation: < 100ms
Best Practices for Performance:
- Token Caching (automatically handled by jdpi_client):
# Tokens are cached automatically with intelligent refresh
auth_client = JDPIClient::Auth::Client.new
token = auth_client.token! # Cached for subsequent requests- Connection Reuse:
# Configure reasonable timeouts
config.timeout = 8 # Request timeout
config.open_timeout = 3 # Connection timeout- Request Batching (where supported):
# Batch DICT lookups when possible
dict_client = JDPIClient::DICT::Keys.new
keys = ["user1@bank.com", "user2@bank.com", "user3@bank.com"]
# Process in batches to respect rate limits1. Exponential Backoff:
def make_request_with_backoff(retries = 3, delay = 1)
begin
spi_client.create_order!(payment_data)
rescue JDPIClient::Errors::RateLimited => e
if retries > 0
sleep(delay)
make_request_with_backoff(retries - 1, delay * 2)
else
raise e
end
end
end2. Rate Limit Monitoring:
# Check rate limit status before making requests
def check_rate_limits(response)
remaining = response.headers['X-RateLimit-Remaining'].to_i
reset_time = response.headers['X-RateLimit-Reset'].to_i
if remaining < 10
wait_time = reset_time - Time.now.to_i
puts "⚠️ Rate limit low (#{remaining} remaining), resets in #{wait_time}s"
end
end3. Request Queue Management:
class JDPIRequestQueue
def initialize(requests_per_minute: 90) # Stay under 100/min limit
@requests_per_minute = requests_per_minute
@request_times = []
end
def throttled_request(&block)
wait_if_needed
result = block.call
@request_times << Time.now
result
end
private
def wait_if_needed
now = Time.now
@request_times.reject! { |time| time < now - 60 } # Keep last minute
if @request_times.size >= @requests_per_minute
sleep_time = 60 - (now - @request_times.first)
sleep(sleep_time) if sleep_time > 0
end
end
end
# Usage
queue = JDPIRequestQueue.new(requests_per_minute: 90)
queue.throttled_request { spi_client.create_order!(payment_data) }Connection Pooling: For high-volume applications, consider implementing connection pooling:
require 'connection_pool'
JDPI_POOL = ConnectionPool.new(size: 25, timeout: 5) do
JDPIClient::SPI::OP.new
end
# Use pooled connections
JDPI_POOL.with do |spi_client|
spi_client.create_order!(payment_data)
endDistributed Rate Limiting: For multiple application instances, use Redis for distributed rate limiting:
class DistributedRateLimit
def initialize(redis, limit:, window:)
@redis = redis
@limit = limit
@window = window
end
def allow_request?(key)
current_time = Time.now.to_i
window_start = current_time - @window
pipe = @redis.pipelined do
@redis.zremrangebyscore(key, 0, window_start)
@redis.zcard(key)
@redis.zadd(key, current_time, current_time)
@redis.expire(key, @window)
end
current_requests = pipe[1]
current_requests < @limit
end
end
# Usage
rate_limiter = DistributedRateLimit.new(
Redis.current,
limit: 90,
window: 60
)
if rate_limiter.allow_request?("jdpi:spi:#{institution_id}")
spi_client.create_order!(payment_data)
else
# Handle rate limit exceeded
raise "Rate limit exceeded"
endKey Metrics to Track:
- Request latency percentiles (P50, P95, P99)
- Rate limit utilization
- Token refresh frequency
- Connection pool utilization
- Error rates by endpoint
Example Monitoring:
class JDPIMetrics
def self.track_request(endpoint, &block)
start_time = Time.now
begin
result = block.call
duration = Time.now - start_time
# Log successful request
Rails.logger.info "JDPI Request", {
endpoint: endpoint,
duration_ms: (duration * 1000).round(2),
status: 'success'
}
result
rescue => e
duration = Time.now - start_time
# Log failed request
Rails.logger.warn "JDPI Request Failed", {
endpoint: endpoint,
duration_ms: (duration * 1000).round(2),
error: e.class.name,
status: 'error'
}
raise e
end
end
end
# Usage
JDPIMetrics.track_request('spi.create_order') do
spi_client.create_order!(payment_data)
end- Cache DICT Lookups: PIX keys don't change frequently
- Batch Operations: Group related requests when possible
- Use Connection Pooling: For multi-threaded applications
- Implement Circuit Breakers: Prevent cascade failures
- Monitor Performance: Track latency and error rates
- Optimize Payload Size: Send only required fields
- Use Compression: Enable gzip compression for large requests
git clone <repository-url>
cd jdpi-client
bundle install# Run all tests
bundle exec rake test
# Run specific test file
bundle exec ruby test/test_config.rb
# Run with verbose output
bundle exec rake test TESTOPTS="-v"
# Run linter
bundle exec rubocop
# Run everything (tests + linting)
bundle exec rake# Auto-fix linting issues
bundle exec rubocop -a
# Run tests with coverage
bundle exec rake test_coverage
# Generate coverage report only
bundle exec rake coverage
# Full CI suite (tests + coverage + linting)
bundle exec rake ci
# Alternative coverage command
COVERAGE=true bundle exec rake testThis project uses GitHub Actions for continuous integration and deployment. Understanding the CI/CD pipeline helps contributors write code that passes all checks.
The CI pipeline is defined in .github/workflows/ci.yml and includes:
-
Multi-Version Testing:
- Tests across Ruby 3.0, 3.1, 3.2, 3.3, and 3.4
- Ensures compatibility across all supported Ruby versions
- Uses matrix strategy for parallel execution
-
Code Quality Checks:
- RuboCop: Linting and style checking
- Test Coverage: Minimum 70% line coverage required
- Security: Dependency vulnerability scanning
-
Test Execution:
- Runs complete test suite (330+ tests, 1800+ assertions)
- Uses mocked HTTP responses for reliable testing
- Includes integration tests for all storage backends
-
Gem Building:
- Validates gem can be built successfully
- Checks gemspec structure and dependencies
Local CI Simulation:
# Run the same checks as CI
bundle exec rake ci
# Individual steps:
bundle exec rubocop # Style checking
bundle exec rake test # Run test suite
COVERAGE=true bundle exec rake test # With coverage
gem build jdpi_client.gemspec # Build gemPre-commit Checks:
# Recommended before committing
bundle exec rubocop -a # Auto-fix style issues
bundle exec rake test # Ensure tests pass
git add . && git commit # Git hooks will clean commit messagesThe CI environment uses these variables:
| Variable | Purpose | Value in CI |
|---|---|---|
CI |
Indicates CI environment | "true" |
COVERAGE |
Enable coverage reporting | "true" |
TEST_ADAPTER |
Default storage adapter for tests | "memory" |
RUBY_VERSION |
Matrix variable for Ruby version | "3.0", "3.1", etc. |
The master branch is protected with these rules:
- Required status checks: All CI jobs must pass
- Up-to-date branches: Must be current with master
- Linear history: No merge commits allowed (use squash/rebase)
Coverage is automatically calculated and reported:
- Coverage threshold: 70% minimum line coverage
- Per-file threshold: 25% minimum per file
- Branch coverage: Tracked but not enforced
- HTML reports: Generated in
coverage/directory - CI comments: Coverage percentage posted on PRs
The CI pipeline automatically:
- Installs system dependencies (libpq-dev, libsqlite3-dev)
- Sets up Ruby with bundler caching
- Installs gems with
bundle install - Runs RuboCop with zero-tolerance for violations
- Executes tests with coverage reporting
- Builds the gem and validates structure
- Reports results via GitHub status checks
Common CI failure scenarios:
-
RuboCop violations:
# Fix locally bundle exec rubocop -a git add . && git commit --amend --no-edit
-
Test failures:
# Run specific test bundle exec ruby test/test_failing_file.rb # Run with verbose output bundle exec rake test TESTOPTS="-v"
-
Coverage drops:
# Check coverage locally COVERAGE=true bundle exec rake test open coverage/index.html # View detailed report
-
Gem build failures:
# Test gem build gem build jdpi_client.gemspec gem spec jdpi_client-*.gem --ruby
CI runs are optimized for speed:
- Bundler caching: Dependencies cached between runs
- Parallel execution: Multiple Ruby versions tested simultaneously
- Minimal system setup: Only installs required dependencies
- Efficient test structure: Fast unit tests with mocked external calls
For contributors, the recommended workflow is:
- Fork and clone the repository
- Create a feature branch (
git checkout -b feature/your-feature) - Make changes following code style guidelines
- Run local CI checks (
bundle exec rake ci) - Commit changes (git hooks clean commit messages)
- Push and create PR - CI runs automatically
- Address CI feedback if any checks fail
- Merge after approval and passing CI
The CI pipeline includes security checks:
- Bundler audit: Scans for known vulnerabilities in dependencies
- Code analysis: Static analysis for potential security issues
- Dependency updates: Dependabot creates PRs for security updates
Running security checks locally:
# Install bundler-audit
gem install bundler-audit
# Check for vulnerabilities
bundle audit check --update
# Update vulnerable dependencies
bundle updateWhen ready to release (maintainers only):
- Update version in
jdpi_client.gemspec - Update CHANGELOG.md with release notes
- Create release PR with version bump
- Merge after CI passes and approval
- Tag release (
git tag v0.2.0 && git push --tags) - Publish gem (when RubyGems integration is ready)
CI pipeline health is monitored through:
- GitHub Actions dashboard: View recent runs and success rates
- Branch protection status: Ensures quality gates are maintained
- Coverage trends: Track coverage changes over time
- Performance metrics: Monitor CI execution time and resource usage
- Current coverage: 75.65% (minimum threshold enforced at 70%)
- Per-file minimum: 25%
- Branch coverage: 53.07%
- HTML reports generated in
coverage/index.html - Branch coverage enabled for detailed analysis
- Works across all supported Ruby versions (3.0+)
- High coverage ensures reliability and maintainability
# Build gem
gem build jdpi_client.gemspec
# Install locally for testing
gem install jdpi_client-*.gem --local
# Uninstall
gem uninstall jdpi_clientThis section covers common issues and their solutions. For more detailed troubleshooting, see the complete troubleshooting guide.
Solution: Install Bundler:
gem install bundlerSolution: Install system dependencies:
# Ubuntu/Debian
sudo apt-get install -y libpq-dev libsqlite3-dev
# macOS
brew install postgresql sqlite3Solution: Ensure Ruby 3.0+ is installed:
ruby --version # Should show 3.0+
rbenv install 3.2.0 # If using rbenv
rbenv global 3.2.0Solution: Set required environment variables:
export JDPI_CLIENT_HOST=api.yourbank.homl.jdpi.pstijd
export JDPI_CLIENT_ID=your_client_id
export JDPI_CLIENT_SECRET=your_client_secretSolution: Check hostname patterns:
# Production hostnames should contain "prod" or "production"
config.jdpi_client_host = "api.bank.prod.jdpi.pstijd" # → HTTPS
# Other hostnames are treated as homolog
config.jdpi_client_host = "api.bank.homl.jdpi.pstijd" # → HTTPSolution:
- Check network connectivity
- Verify firewall settings
- For development/testing only, you can disable SSL verification:
# ⚠️ DEVELOPMENT/TESTING ONLY - NEVER in production!
config.verify_ssl = false # If this option existsSolution: Verify credentials and configuration:
- Check credentials:
# Test configuration
puts "Host: #{JDPIClient.config.jdpi_client_host}"
puts "Client ID: #{JDPIClient.config.oauth_client_id}"
puts "Secret set: #{!JDPIClient.config.oauth_secret.nil?}"- Test authentication directly:
auth_client = JDPIClient::Auth::Client.new
begin
token = auth_client.token!
puts "✅ Authentication successful"
puts "Token expires: #{auth_client.token_info[:expires_at]}"
rescue => e
puts "❌ Authentication failed: #{e.message}"
end- Check token storage:
# Clear cached tokens to force refresh
auth_client.refresh!Solution:
- Verify credentials haven't expired
- Check network connectivity to JDPI
- Clear token storage and retry
- Contact JDPI support if issue persists
Solution: Implement proper rate limiting:
begin
response = spi_client.create_order!(payment_data)
rescue JDPIClient::Errors::RateLimited => e
sleep(60) # Wait for rate limit reset
retry
endSolution: Check request format:
# Enable debug logging to see request details
JDPIClient.config.logger.level = Logger::DEBUG
# Common validation issues:
payment_data = {
valor: 1050, # Amount in centavos (required)
chave: "user@bank.com", # Valid PIX key (required)
dt_hr_requisicao_psp: Time.now.utc.iso8601, # ISO8601 format (required)
idempotency_key: SecureRandom.uuid # Unique key (recommended)
}Solution: Adjust timeout settings:
JDPIClient.configure do |config|
config.timeout = 15 # Increase request timeout
config.open_timeout = 5 # Increase connection timeout
endSolution: Verify Redis configuration:
# Test Redis connectivity
redis-cli -u $REDIS_URL ping# Test Redis from Ruby
require 'redis'
redis = Redis.new(url: ENV['REDIS_URL'])
redis.ping # Should return "PONG"Solution: Verify database setup:
# Test database connectivity
require 'active_record'
ActiveRecord::Base.establish_connection(ENV['DATABASE_URL'])
ActiveRecord::Base.connection.execute("SELECT 1")Solution: Verify AWS configuration:
# Check AWS credentials
aws sts get-caller-identity
# Test DynamoDB access
aws dynamodb list-tables --region us-east-1Solution:
- Check network latency to JDPI endpoints
- Enable connection pooling for multi-threaded apps
- Monitor token caching effectiveness
- Verify rate limiting isn't being triggered
Solution:
- Ensure proper connection cleanup
- Monitor token storage size
- Use connection pooling instead of creating new clients
- Review application architecture for memory leaks
Solution: Use test stubs and mocks:
# In test_helper.rb or spec_helper.rb
require 'webmock/minitest' # or webmock/rspec
# Stub OAuth requests
stub_request(:post, %r{.*/auth/jdpi/connect/token})
.to_return(
status: 200,
body: {
access_token: "test_token",
token_type: "Bearer",
expires_in: 3600
}.to_json,
headers: { "Content-Type" => "application/json" }
)Solution: Run tests with coverage enabled:
COVERAGE=true bundle exec rake testSolution: Auto-fix style issues:
bundle exec rubocop -a # Auto-fix issues
bundle exec rubocop # Check remaining issuesJDPIClient.configure do |config|
config.logger = Logger.new($stdout)
config.logger.level = Logger::DEBUG
endconfig = JDPIClient.config
puts "Environment: #{config.environment}"
puts "Base URL: #{config.base_url}"
puts "Timeout: #{config.timeout}s"
puts "Token storage: #{config.token_storage_adapter}"auth_client = JDPIClient::Auth::Client.new
info = auth_client.token_info
puts "Token cached: #{info[:cached]}"
puts "Storage type: #{info[:storage_type]}"
puts "Expires at: #{info[:expires_at]}"
puts "Time until expiry: #{info[:expires_at] - Time.now} seconds" if info[:expires_at]If you're still experiencing issues:
- Check the logs for detailed error messages
- Review the FAQ section for common questions
- Search existing issues on GitHub
- Create a minimal reproduction case
- Open a GitHub issue with:
- Ruby version
- Gem version
- Configuration (sanitized)
- Full error message and stack trace
- Steps to reproduce
We welcome contributions! Please follow these steps:
-
Fork the repository
-
Create a feature branch
git checkout -b feature/your-feature-name
-
Make your changes following our coding standards:
- Use frozen string literals
- Follow existing code style and patterns
- Add tests for new functionality
- Update documentation as needed
-
Run the test suite
bundle exec rake test bundle exec rubocop
-
Commit your changes
git commit -am "Add feature: your feature description" -
Push to your fork
git push origin feature/your-feature-name
-
Create a Pull Request
- Provide a clear description of your changes
- Reference any related issues
- Ensure CI tests pass
- Follow the existing code style
- Use meaningful variable and method names
- Add comments for complex business logic
- Keep methods focused and concise
- Use proper error handling
- Write tests for all new functionality
- Use descriptive test names
- Mock external API calls
- Test both success and error scenarios
- Maintain good test coverage
/docs- Complete JDPI API documentation and PIX rulesCLAUDE.md- Claude Code specific configuration and patternsdocs/13-Claude-Examples.md- Comprehensive usage examplesdocs/14-Development-Workflow.md- Development and testing guidedocs/15-Troubleshooting.md- Common issues and solutions
To get dynamic badges that update automatically:
-
CodeCov Integration (recommended):
# Add to .github/workflows/ci.yml - name: Upload coverage to Codecov uses: codecov/codecov-action@v3 with: file: ./coverage/coverage.xml
Badge:
[](https://codecov.io/gh/agramms/jdpi-client) -
GitHub Pages Coverage:
# Add job to deploy coverage reports - name: Deploy coverage to GitHub Pages uses: peaceiris/actions-gh-pages@v3 with: github_token: ${{ secrets.GITHUB_TOKEN }} publish_dir: ./coverage
For automatically updating badges, consider:
- GitHub Actions: Auto-update README badges based on test results
- Shields.io: Dynamic badges from GitHub API endpoints
- CodeClimate: Code quality and coverage integration
- RubyGems: Automatic gem version badges
All badges are configured with current project metrics:
- CI Status: Links to GitHub Actions workflow results
- Coverage: Shows current 75.65% test coverage
- Ruby Support: Indicates Ruby 3.0+ compatibility
- License: MIT license badge
- Code Style: RuboCop compliance
Q: What is JDPI and how does it relate to PIX? A: JDPI (Java Development Platform Integration) is the platform that facilitates PIX payments in Brazil. PIX is the instant payment system created by the Central Bank of Brazil. This gem provides a Ruby client to interact with JDPI services for PIX operations.
Q: Do I need to be a bank to use this gem? A: Not necessarily. You need to be a PIX participant, which includes banks, payment institutions, and other authorized financial entities. You must obtain credentials from the Central Bank of Brazil.
Q: Is this gem production-ready? A: Yes, the gem is designed for production use with comprehensive error handling, token management, rate limiting, and security features. It has 75.65% test coverage and supports Ruby 3.0+.
Q: Why do I get "gem not found" when trying to install? A: This gem is currently available from source. Use the Git installation method shown in the Installation section. Publishing to RubyGems is planned for future releases.
Q: What Ruby versions are supported? A: Ruby 3.0 and higher are supported. The gem is tested on Ruby 3.0, 3.1, 3.2, 3.3, and 3.4.
Q: Do I need Rails to use this gem? A: No, this gem has no Rails dependency and works with any Ruby application (Sinatra, plain Ruby scripts, etc.).
Q: How do I know if my configuration is correct? A: Use the verification script from the Quick Setup Checklist section. It will test your configuration and authentication.
Q: What's the difference between production and homolog environments? A: The gem auto-detects environments based on hostname:
- Hostnames containing "prod" or "production" use HTTPS and production settings
- All other hostnames use HTTP and are treated as homolog/testing environments
Q: Should I enable token encryption? A: Yes, for production environments. Token encryption protects sensitive OAuth tokens when stored in Redis, databases, or other storage backends.
Q: How often do tokens expire? A: OAuth tokens typically expire after 1 hour. The gem automatically handles token refresh, so you don't need to manage this manually.
Q: Can I share tokens between application instances? A: Yes, use Redis, Database, or DynamoDB token storage adapters for sharing tokens across multiple application instances or servers.
Q: Why am I getting authentication errors? A: Common causes:
- Invalid client credentials
- Incorrect hostname/environment
- Network connectivity issues
- Expired credentials (contact JDPI support)
Q: What's the difference between SPI OP and SPI OD? A: - SPI OP: Outbound payments (initiating PIX payments)
- SPI OD: Inbound operations (refunds, disputes, queries)
Q: How do I handle PIX key validation? A: Use the DICT services to validate PIX keys before creating payments:
dict_client = JDPIClient::DICT::Keys.new
key_info = dict_client.consult_key("user@bank.com")Q: What is idempotency and why is it important?
A: Idempotency ensures that duplicate requests don't create multiple payments. Always provide a unique idempotency_key for payment operations:
response = spi_client.create_order!(
# ... payment data ...
idempotency_key: SecureRandom.uuid
)Q: How do I handle rate limiting?
A: The gem provides JDPIClient::Errors::RateLimited exceptions. Implement exponential backoff and monitor rate limit headers (see Performance section).
Q: What should I do when payments fail? A: Always implement proper error handling for different scenarios:
- Validation errors (400): Fix the request data
- Authentication errors (401): Check credentials/tokens
- Rate limiting (429): Implement backoff/retry logic
- Server errors (5xx): Log and retry with exponential backoff
Q: How do I debug API calls? A: Enable debug logging:
config.logger = Logger.new($stdout)
config.logger.level = Logger::DEBUGQ: How many requests per second can I make? A: JDPI has rate limits (typically 100 requests/minute for PIX payments). See the Performance & Rate Limiting section for details and optimization strategies.
Q: Can this gem handle high-volume applications? A: Yes, with proper configuration:
- Use connection pooling for multi-threaded applications
- Implement distributed rate limiting with Redis
- Use appropriate token storage backends
- Monitor performance metrics
Q: Should I use connection pooling?
A: For multi-threaded applications or high-volume scenarios, yes. Use the connection_pool gem as shown in the Performance section.
Q: How should I store credentials securely? A: - Use environment variables, never hard-code credentials
- Enable token encryption in production
- Use dedicated secret management services
- Rotate credentials regularly
Q: What should I log and what shouldn't I log? A: Log: Request metadata, response status, timing, error messages Never log: OAuth tokens, client secrets, sensitive payment data, PIX keys
Q: Is it safe to use in production? A: Yes, when following security best practices:
- Use HTTPS in production
- Enable token encryption
- Implement proper error handling
- Monitor for security events
- Follow the Security Considerations section
Q: Tests are failing with authentication errors A: Ensure your test environment uses mock/stub HTTP requests. The gem includes comprehensive test helpers for this purpose.
Q: I'm getting SSL certificate errors A: This usually indicates:
- Network connectivity issues
- Firewall blocking HTTPS traffic
- Invalid/expired certificates (rare with JDPI)
Q: Token storage isn't working A: Verify:
- Storage backend is properly configured and accessible
- Required gems are installed (redis, pg, aws-sdk-dynamodb)
- Network connectivity to storage services
- Proper credentials/permissions
Q: Performance is slower than expected A: Check:
- Network latency to JDPI endpoints
- Token caching is working (should see cached token reuse)
- Connection pooling configuration
- Rate limiting isn't being triggered
- Database/Redis performance if using those storage backends
Q: How do I upgrade to a new version?
A: Update the gem reference in your Gemfile and run bundle update jdpi_client. Check the CHANGELOG for breaking changes.
Q: Can I migrate from another PIX client? A: Yes, but you'll need to:
- Update configuration format
- Adjust API call patterns
- Update error handling
- Test thoroughly in homolog environment
Q: How do I contribute to this project? A: See the Contributing section for guidelines on submitting issues, feature requests, and pull requests.
This project is licensed under the MIT License - see the LICENSE file for details.
For issues and questions:
- Check the troubleshooting guide
- Review existing documentation
- Open an issue on GitHub