= 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.