Skip to content

SDK-SPEC-RUST — Rust SDK Specification

Status: Draft Owner: Platform Engineering Crate: simpaisa Runtime: Rust 1.75+ (2024 edition) Last Updated: 2026-04-03


1. Overview

Official Rust SDK for the Simpaisa Payment Gateway. Provides strongly-typed, async access to Pay-Ins, Pay-Outs, Remittances, and Cards across PK, BD, NP, and IQ corridors. Handles 270M+ annual transactions at $1B+ volume. Used internally for Unikraft-based microservices. Published to crates.io as simpaisa.

2. Crate Configuration

[dependencies]
simpaisa = "1"

Feature flags: - default = ["tokio-runtime"] - tokio-runtime — tokio async runtime (default) - blocking — synchronous blocking client - webhook — webhook verification support - tracing — OpenTelemetry tracing integration

3. Module Structure

simpaisa/
├── src/
│   ├── lib.rs                  # Public API re-exports
│   ├── client.rs               # SimPaisaClient struct
│   ├── config.rs               # Config and Environment enum
│   ├── payin/
│   │   ├── mod.rs              # PayIn client (initiate, verify, status, list)
│   │   ├── request.rs          # PayInRequest struct
│   │   └── response.rs         # PayInResponse struct
│   ├── payout/
│   │   ├── mod.rs              # PayOut client (initiate, status, batch, cancel)
│   │   ├── request.rs
│   │   └── response.rs
│   ├── remittance/
│   │   ├── mod.rs              # Remittance client (quote, initiate, status, beneficiaries)
│   │   ├── request.rs
│   │   └── response.rs
│   ├── cards/
│   │   ├── mod.rs              # Cards client (pay, capture, void, refund, tokenise)
│   │   ├── request.rs
│   │   └── response.rs
│   ├── webhook/
│   │   ├── mod.rs              # WebhookVerifier
│   │   └── event.rs            # WebhookEvent struct
│   ├── auth/
│   │   ├── mod.rs              # AuthProvider trait
│   │   └── rsa.rs              # RSAAuthProvider — PEM loading, request signing
│   ├── error.rs                # Error enum (thiserror)
│   ├── retry.rs                # RetryPolicy with exponential backoff
│   ├── idempotency.rs          # IdempotencyKeyGenerator (UUID v7)
│   └── types.rs                # Shared types (Currency, Amount, etc.)
├── tests/
│   ├── integration/
│   └── common/
├── Cargo.toml
└── rust-toolchain.toml

4. Authentication — RSAAuthProvider

  • Load merchant private key from PEM file via ring::signature
  • Sign request body with RSA-PKCS1-SHA256
  • ring crate for all cryptographic operations (no OpenSSL dependency)
  • Implement AuthProvider trait for extensibility
  • Support key rotation via Vec<RsaKeyPair>
let auth = RsaAuthProvider::from_pem(
    "MCH-001",
    include_bytes!("../keys/merchant.pem"),
)?;

5. Async (tokio)

  • All public methods SHALL be async fn returning Result<T, SimPaisaError>
  • Built on reqwest HTTP client (tokio-native)
  • Connection pooling via reqwest::Client (shared, cloneable)
  • Optional blocking feature flag for synchronous usage
  • No .block_on() calls in async code paths
let result = client.payin().initiate(&request).await?;

6. Zero-Copy Deserialisation

  • serde with #[derive(Deserialize)] on all response types
  • serde_json for JSON parsing
  • Use &str and Cow<'_, str> where zero-copy is beneficial
  • #[serde(borrow)] annotations for borrowed data
  • Benchmark deserialisation performance in CI

7. WebhookVerifier

  • Verify X-Simpaisa-Webhook-Signature using HMAC-SHA256 via ring::hmac
  • fn verify(&self, payload: &[u8], signature: &str) -> Result<(), SimPaisaError>
  • Constant-time comparison via ring::hmac::verify()
  • Replay protection: validate timestamp header within tolerance (default 300s)
  • Gated behind webhook feature flag

8. Retry

  • Exponential backoff with jitter (base 200ms, max 30s)
  • Configurable max retries (default 3)
  • Retry on: 429, 502, 503, 504, connection errors
  • Never retry: 400, 401, 403, 404, 409, 422
  • Respect Retry-After header
  • on_retry closure for observability
  • Tokio sleep for async delays

9. Strong Typing

  • Currency enum (PKR, BDT, NPR, IQD) — not raw strings
  • Amount newtype wrapping rust_decimal::Decimal
  • MerchantId, TransactionId newtypes
  • Channel enum for payment channels per corridor
  • Environment enum: Sandbox, Production
  • Compile-time validation where possible via type system

10. Error Handling

#[derive(Debug, thiserror::Error)]
pub enum SimPaisaError {
    #[error("authentication failed: {message}")]
    Authentication { code: String, message: String, status: u16 },

    #[error("validation failed: {message}")]
    Validation { code: String, message: String, fields: Vec<FieldError> },

    #[error("rate limited, retry after {retry_after:?}")]
    RateLimit { retry_after: Duration },

    #[error("request timed out")]
    Timeout(#[source] reqwest::Error),

    #[error("idempotency conflict")]
    Idempotency { request_id: String },

    #[error("network error: {0}")]
    Network(#[source] reqwest::Error),

    #[error("webhook verification failed")]
    WebhookVerification,
}

11. Configuration

let client = SimPaisaClient::builder()
    .environment(Environment::Sandbox)
    .merchant_id("MCH-001")
    .private_key_pem(include_bytes!("../keys/merchant.pem"))
    .timeout(Duration::from_secs(30))     // default 30s
    .max_retries(3)                       // default 3
    .build()?;

12. Example Usage

use simpaisa::{SimPaisaClient, Environment};
use simpaisa::payin::PayInRequest;
use simpaisa::webhook::WebhookVerifier;

#[tokio::main]
async fn main() -> Result<(), simpaisa::SimPaisaError> {
    let client = SimPaisaClient::builder()
        .environment(Environment::Sandbox)
        .merchant_id("MCH-001")
        .private_key_file("./keys/merchant.pem")
        .build()?;

    // Pay-In
    let pay_in = client.payin().initiate(&PayInRequest {
        amount: "5000".parse()?,
        currency: Currency::PKR,
        channel: Channel::Easypaisa,
        customer_msisdn: "03001234567".into(),
        callback_url: "https://merchant.com/webhook".parse()?,
    }).await?;

    // Webhook verification
    let verifier = WebhookVerifier::new(b"whsec_...");
    verifier.verify(raw_body, &signature_header)?;

    Ok(())
}

13. Build and Publish

  • Build: cargo build (debug + release profiles)
  • Test: cargo test with cargo-nextest in CI
  • Linting: cargo clippy -- -D warnings
  • Formatting: cargo fmt --check
  • Audit: cargo audit for vulnerability scanning
  • CI: Bitbucket Pipelines — fmt → clippy → test → audit → publish
  • Publish: crates.io with API token
  • Versioning: Semantic versioning
  • MSRV: 1.75 (documented in Cargo.toml and rust-toolchain.toml)

14. Unikraft Integration

  • Compile to static binary for Unikraft unikernel deployment
  • No dynamic linking (lto = true, codegen-units = 1 in release profile)
  • Minimal binary size via opt-level = "z" + strip = true
  • Compatible with #![no_std] for core types (future consideration)

15. Testing Requirements

  • Unit tests for every public function (>90% coverage via cargo-tarpaulin)
  • Integration tests against sandbox environment (behind --features integration)
  • Property-based tests via proptest for serialisation round-trips
  • Webhook verification round-trip tests
  • Retry behaviour tests with wiremock
  • Benchmark tests via criterion for critical paths
  • Miri for undefined behaviour detection on unsafe code (if any)