iran-pay 0.1.3

Unified async SDK for Iranian payment gateways — ZarinPal, IDPay, NextPay, Pay.ir behind one strongly-typed Rust trait.
Documentation
= iran-pay

image:https://img.shields.io/crates/v/iran-pay.svg[crates.io,link=https://crates.io/crates/iran-pay]
image:https://docs.rs/iran-pay/badge.svg[docs.rs,link=https://docs.rs/iran-pay]
image:https://github.com/obsernetics/rust-lib/actions/workflows/ci.yml/badge.svg[CI,link=https://github.com/obsernetics/rust-lib/actions/workflows/ci.yml]
image:https://img.shields.io/badge/license-Apache--2.0-blue.svg[License]
image:https://img.shields.io/badge/MSRV-1.88-orange.svg[MSRV]
image:https://deps.rs/repo/github/obsernetics/rust-lib/status.svg[Dependencies,link=https://deps.rs/repo/github/obsernetics/rust-lib]

Unified async SDK for Iranian payment gateways.

== Features

* *ZarinPal* driver — production v4 JSON API + sandbox.
* *IDPay* driver — `X-API-KEY` JSON API + sandbox header.
* *NextPay* driver — form-encoded API with `code: -1` semantics.
* *Pay.ir* driver — form-encoded API with the magic `"test"` sandbox key.
* Single dyn-safe `Gateway` trait — swap providers with one line at runtime.
* `Amount` type with explicit Toman / Rial unit constructors — eliminates the
  most common Iranian-payment-integration bug at the type level.
* Built-in `MockGateway` for fast, deterministic, network-free unit tests of
  your checkout code.
* Re-export of the `parsitext` Iranian validators (national ID, IBAN,
  bank-card, mobile, postal code, car plate) — pre-flight buyer input
  before you call the gateway.
* `tracing` instrumentation on every driver method (provider name, amount,
  authority surfaced as fields).
* Strongly-typed `thiserror` error enum: transport, gateway-business, amount
  mismatch, configuration, decode, and unsupported-operation variants are all
  separate.
* Async-first (`async-trait`), Tokio-friendly, no blocking calls.

=== Optional features

[cols="1,1,3", options="header"]
|===
| Feature       | Default | What it enables

| `zarinpal`    | ✓       | Compile the `providers::ZarinPal` driver
| `idpay`       | ✓       | Compile the `providers::IDPay` driver
| `nextpay`     | ✓       | Compile the `providers::NextPay` driver
| `payir`       | ✓       | Compile the `providers::PayIr` driver
| `validators`  | ✓       | Re-export `parsitext`'s Iranian validators
| `rustls-tls`  | ✓       | Use rustls for HTTPS (no system OpenSSL needed)
| `native-tls`  |         | Use the platform TLS library instead of rustls
|===

Disabling all four provider features still builds the trait, types, and
mock gateway — useful when you bring your own driver against this
abstraction.

== Quick start

[source,toml]
----
[dependencies]
iran-pay = "0.1.2"
tokio    = { version = "1", features = ["macros", "rt-multi-thread"] }

# Or pick only the providers you need:
iran-pay = { version = "0.1.2", default-features = false, features = ["zarinpal", "rustls-tls"] }
----

[source,rust]
----
use iran_pay::providers::ZarinPal;
use iran_pay::{Amount, Gateway, StartRequest, VerifyRequest};

#[tokio::main]
async fn main() -> Result<(), iran_pay::Error> {
    let gateway = ZarinPal::new("YOUR-MERCHANT-UUID").sandbox();

    // 1. Initiate the payment.
    let start = gateway.start_payment(&StartRequest::builder()
        .amount(Amount::toman(50_000))
        .description("Pro subscription — May 2026")
        .callback_url("https://example.com/payment/callback")
        .order_id("ORD-12345")
        .build()).await?;

    // 2. Redirect the user to `start.payment_url`.
    println!("Send user to: {}", start.payment_url);

    // 3. After they return, verify.  Pass the same amount back in to
    //    catch tampering with the callback query string.
    let verified = gateway.verify_payment(&VerifyRequest {
        authority: start.authority,
        amount: Amount::toman(50_000),
    }).await?;

    println!("Paid! Transaction ID = {}", verified.transaction_id);
    Ok(())
}
----

== Examples

Each runnable example lives in `examples/`:

[cols="1,2", options="header"]
|===
| Example            | What it demonstrates

| `zarinpal_basic`   | Minimal start-payment flow against the ZarinPal sandbox.
| `multi_gateway`    | `Box<dyn Gateway>` polymorphism — register all four providers and look them up by key.
| `with_validators`  | Use the `validators` re-export to check bank cards / mobiles / operators before calling the gateway.
| `mock_in_test`     | Drop `MockGateway` into your own service tests; assert call counts and script failures.
|===

Run with:

[source,bash]
----
cargo run --example <name> -p iran-pay
----

== Comparison

[cols="1,1,1,1", options="header"]
|===
| Capability                       | `iran-pay`              | JS `iranianbank` / `iranian-bank-gateways` | Hand-rolled `reqwest`

| Unified `Gateway` trait          | ✓                        | partial (per-provider classes)              | ✗
| Typed `Amount` (Toman / Rial)    | ✓ (compile-time)         | ✗ (raw numbers)                             | ✗
| Async / Tokio-native             | ✓                        | ✓ (Node)                                    | yes (manual)
| `tracing` instrumentation        | ✓ (built-in spans)        | ✗                                           | manual
| Mock driver shipped with the SDK | ✓                        | ✗                                           | ✗
| Strong error taxonomy            | ✓ (`thiserror`)          | strings / `Error`                           | ad-hoc
| Iranian validators bundled       | ✓ (via `parsitext`)      | separate package                            | ✗
|===

== Documentation

[source,bash]
----
cargo doc -p iran-pay --all-features
----

Hosted docs: link:https://docs.rs/iran-pay[docs.rs/iran-pay].

== License

Apache-2.0.