upi-rs 0.1.0

A type-safe, provider-agnostic Rust crate for UPI payment integrations.
Documentation

upi-rs: Type-Safe UPI Payment Integration

Crates.io Documentation License: MIT

A production-ready, type-safe, asynchronous, and provider-agnostic Rust crate for integrating UPI (Unified Payments Interface) payment processing into your applications.

Philosophy

"Parse, don't validate." This crate enforces type safety at compile time, ensuring that illegal states (like negative amounts or invalid VPAs) are unrepresentable in the type system.

Features

✨ Type-Safe Domain Models: Invalid payment data cannot be constructed
πŸ”— Provider Agnostic: Swap payment providers without changing your code
⚑ Async-First: Built on tokio for modern async Rust
πŸ” Cryptographic Verification: Constant-time HMAC-SHA256 signature verification
πŸ“¦ Zero-Cost Abstractions: Efficient implementations with minimal overhead
πŸ§ͺ Comprehensive Testing: Unit tests for all components

Installation

Add this to your Cargo.toml:

[dependencies]
upi-rs = "0.1"
tokio = { version = "1.32", features = ["full"] }

Quick Start

1. Create Domain Objects

use upi_rs::types::{Vpa, Amount};

// UPI address - validated at construction time
let vpa = Vpa::new("customer@upi")?;

// Monetary amount - stored in Paise (1 INR = 100 Paise)
let amount = Amount::from_inr(100.50)?;

2. Initialize a Provider

use upi_rs::RazorpayProvider;

let provider = RazorpayProvider::new(
    "your_key_id",
    "your_key_secret"
);

3. Process Payments

use upi_rs::traits::PaymentRequest;

let request = PaymentRequest::new(vpa, amount, "TXN001".to_string());

match provider.create_payment(request).await {
    Ok(response) => println!("Payment ID: {}", response.id),
    Err(e) => eprintln!("Payment failed: {}", e),
}

4. Verify Webhooks

if provider.verify_webhook(payload, signature) {
    println!("Webhook is authentic!");
}

Type Safety Examples

VPA Validation

VPAs are validated using a regex pattern to ensure format correctness:

use upi_rs::types::Vpa;

// βœ… Valid VPAs
Vpa::new("john@upi")?;
Vpa::new("alice.doe@okaxis")?;
Vpa::new("bob-smith@oksbi")?;

// ❌ Invalid VPAs (compile-time rejection)
Vpa::new("invalid-vpa")?;        // Missing @
Vpa::new("jo$hn@upi")?;          // Invalid character
Vpa::new("@upi")?;               // Missing username

Amount Validation

Amounts are stored internally in Paise to avoid floating-point precision issues:

use upi_rs::types::Amount;

// βœ… Valid amounts
Amount::from_inr(100.0)?;
Amount::from_inr(10.50)?;
Amount::from_inr(0.01)?;

// ❌ Invalid amounts (type-level rejection)
Amount::from_inr(0.0)?;          // Zero amount
Amount::from_inr(-50.0)?;        // Negative
Amount::from_inr(10.555)?;       // Too many decimals

Module Overview

Module Purpose
types Domain types with strict validation (Vpa, Amount)
error Unified error type for all operations
security Cryptographic verification (HMAC-SHA256)
traits Provider abstraction layer
providers Concrete gateway implementations (Razorpay, etc.)

Architecture

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚          Your Application               β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
             β”‚
             β–Ό
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚     UpiProvider Trait (Abstraction)     β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚  β€’ create_payment()                     β”‚
β”‚  β€’ verify_webhook()                     β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
             β”‚
    β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”
    β–Ό        β–Ό        β–Ό
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚Razorpayβ”‚ β”‚Cashfreeβ”‚ β”‚Custom  β”‚
β”‚Providerβ”‚ β”‚Providerβ”‚ β”‚Providerβ”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Error Handling

The crate provides a unified error type with descriptive messages:

use upi_rs::UpiError;

match create_payment().await {
    Ok(response) => println!("Success: {}", response.id),
    Err(UpiError::ValidationError(msg)) => eprintln!("Validation: {}", msg),
    Err(UpiError::ProviderError(msg)) => eprintln!("Provider: {}", msg),
    Err(UpiError::NetworkError(err)) => eprintln!("Network: {}", err),
    Err(UpiError::SignatureError) => eprintln!("Signature verification failed"),
    Err(UpiError::SerializationError(err)) => eprintln!("JSON error: {}", err),
}

Testing

Run the comprehensive test suite:

# Unit tests
cargo test

# Documentation tests
cargo test --doc

# Strict linting
cargo clippy -- -D warnings

# Code formatting
cargo fmt

Example

See examples/simple_payment.rs for a complete working example:

cargo run --example simple_payment

Security Considerations

  1. API Keys: Never hardcode API keys. Use environment variables or secure vaults.
  2. HTTPS Only: All API communications use HTTPS/TLS.
  3. Constant-Time Comparison: Webhook signatures are verified using constant-time comparison to prevent timing attacks.
  4. Secret Management: API secrets are wrapped in secrecy::Secret to prevent accidental leakage in debug output.

Roadmap

  • Additional provider implementations (Cashfree, PhonePe, PayU)
  • Request signing for enhanced security
  • Webhook server utilities
  • Retry policies and circuit breakers
  • Rate limiting support
  • Detailed audit logging

Contributing

Contributions are welcome! Please ensure:

  1. All tests pass: cargo test
  2. Code is formatted: cargo fmt
  3. No clippy warnings: cargo clippy -- -D warnings
  4. Documentation is complete for all public APIs

License

Licensed under the MIT License (LICENSE or http://opensource.org/licenses/MIT).

Disclaimer

This crate is provided as-is for integration purposes. Always test thoroughly with real payment credentials in a staging environment before deploying to production. Consult with your payment provider's documentation for specific integration details and compliance requirements.

Support

For issues, questions, or feature requests, please open an issue on GitHub.


Made with ❀️ for the Rust payments ecosystem