Rust SDK
The official Seev SDK for Rust. Async-first with Tokio support, works with Axum, Actix-web, and any Rust environment.
This SDK is currently a work in progress. The API surface below reflects the planned interface and may change before the stable release. Follow the changelog for updates.
The Seev Rust SDK (seev) wraps the Seev REST API with a type-safe, async-first Rust interface built on reqwest and tokio. It works with any async runtime — Axum, Actix-web, Warp, or standalone Tokio applications.
Installation
Add the crate to your Cargo.toml:
[dependencies]
seev = "0.1"
tokio = { version = "1", features = ["full"] }Initialise
Call Seev::init() once at startup before making any API calls.
use seev::Seev;
#[tokio::main]
async fn main() {
Seev::init(seev::Config {
public_key: "pk_your_public_key".to_string(),
private_key: "sk_your_secret_key".to_string(),
..Default::default()
})
.expect("Failed to initialise Seev SDK");
}Never expose your private_key in client-side code or commit it to version control. Use environment variables.
Create a charge
Seev::charge() creates a checkout session and returns a struct containing the URL to redirect your customer to.
use seev::{Seev, ChargeRequest, CheckoutRecipient};
let charge = Seev::charge(ChargeRequest {
charge_type: "checkout".to_string(),
recipient: CheckoutRecipient {
name: "Jane Doe".to_string(),
email: "jane@example.com".to_string(),
phone: Some("+233501234567".to_string()),
..Default::default()
},
amount: Some(10000), // smallest currency unit — 10000 = GHS 100.00
currency: "GHS".to_string(),
channels: Some(vec!["card".to_string(), "mobile_money".to_string()]),
redirect_url: "https://myapp.com/callback".to_string(),
..Default::default()
})
.await?;
println!("{}", charge.id); // checkout session ID
println!("{}", charge.checkout_url); // redirect your customer hereCharge from items
Pass items instead of amount to have Seev calculate the total:
use seev::{Seev, ChargeRequest, CheckoutRecipient, CheckoutItem};
let charge = Seev::charge(ChargeRequest {
charge_type: "checkout".to_string(),
recipient: CheckoutRecipient {
name: "John Doe".to_string(),
email: "john@example.com".to_string(),
..Default::default()
},
items: Some(vec![
CheckoutItem { name: "Product A".to_string(), price: 5000, quantity: Some(2), ..Default::default() },
CheckoutItem { name: "Product B".to_string(), price: 3000, quantity: Some(1), ..Default::default() },
]),
currency: "GHS".to_string(),
redirect_url: "https://myapp.com/callback".to_string(),
..Default::default()
})
.await?;Charge struct
| Field / Method | Type | Description |
|---|---|---|
charge.id | String | Checkout session ID |
charge.checkout_url | String | URL to redirect the customer to |
charge.status | String | Initial session status |
charge.amount | i64 | Resolved charge amount |
charge.currency | String | Charge currency |
charge.get_response() | serde_json::Value | Full API response |
charge.get_raw(key) | Option<serde_json::Value> | Access any field from the raw response |
Handle the callback
Seev::callback() verifies the session after the customer returns from the checkout page.
// Axum handler example
use axum::{extract::Query, response::Json};
use std::collections::HashMap;
async fn handle_callback(Query(params): Query<HashMap<String, String>>) -> Json<serde_json::Value> {
let session_id = params.get("session_id").cloned().unwrap_or_default();
let session = Seev::callback(&session_id).await.unwrap();
if session.is_successful() {
Json(serde_json::json!({ "status": "success" }))
} else {
Json(serde_json::json!({ "status": session.status }))
}
}Callback struct
| Field / Method | Type | Description |
|---|---|---|
session.id | String | Session ID |
session.status | String | Payment status |
session.reference | String | Transaction reference |
session.is_successful() | bool | true if payment completed |
session.get_session() | serde_json::Value | Full session object |
Handle webhooks
Seev::webhook() parses incoming Seev webhook events. Always return 200 quickly and process asynchronously.
// Axum handler example
use axum::{extract::Json as BodyJson, response::Json};
use serde_json::Value;
async fn handle_webhook(BodyJson(body): BodyJson<Value>) -> Json<serde_json::Value> {
let event = Seev::webhook(body, None).await.unwrap();
if event.is("checkout.completed") {
let data = event.get_data();
println!("Payment completed: {:?}", data);
} else if event.is("checkout.failed") {
println!("Payment failed: {:?}", event.get_data());
} else if event.is("refund.issued") {
println!("Refund issued: {:?}", event.get_data());
} else {
println!("Unhandled event: {}", event.event_type());
}
Json(serde_json::json!({ "received": true }))
}Webhook struct
| Field / Method | Description |
|---|---|
event.event_type() | Event type string, e.g. "checkout.completed" |
event.is(type) | true if the event matches the given type |
event.get_data() | Event payload as serde_json::Value |
event.user() | Associated user (if passed as second arg to webhook()) |
event.get_raw_event() | Full raw event object |
Common webhook events:
| Event | Description |
|---|---|
checkout.completed | Payment succeeded |
checkout.failed | Payment failed |
checkout.pending | Payment is pending confirmation |
refund.issued | A refund was issued |
invoice.created | Invoice was created |
invoice.paid | Invoice was paid |
Fetch a transaction
Seev::transaction() retrieves details about a specific transaction. Use this server-side to confirm payment status.
let txn = Seev::transaction("txn_abc123xyz").await?;
println!("Status: {} — Amount: {} {}", txn.status, txn.amount, txn.currency);Transaction struct
| Field | Type | Description |
|---|---|---|
txn.id | String | Transaction ID |
txn.status | String | "completed", "pending", "failed" |
txn.amount | i64 | Transaction amount |
txn.currency | String | Transaction currency |
txn.reference | Option<String> | Optional reference |
Type reference
pub struct Config {
pub public_key: String,
pub private_key: String,
pub base_url: Option<String>, // defaults to production URL
pub timeout_secs: Option<u64>, // defaults to 30
}
pub struct ChargeRequest {
pub charge_type: String, // "checkout" | "invoice" | "payment_link"
pub recipient: CheckoutRecipient,
pub currency: String, // GHS | USD | EUR | GBP | NGN | USDC | USDT | ETH | BTC | XRP
pub channels: Option<Vec<String>>, // card | mobile_money | bank_transfer | seev | crypto
pub amount: Option<i64>, // omit if using items
pub items: Option<Vec<CheckoutItem>>,
pub discount: Option<f64>,
pub tax: Option<f64>,
pub redirect_url: String,
}
pub struct CheckoutRecipient {
pub name: String,
pub email: String,
pub phone: Option<String>,
pub address: Option<String>,
pub city: Option<String>,
}
pub struct CheckoutItem {
pub name: String,
pub price: i64,
pub quantity: Option<i64>,
pub description: Option<String>,
pub image: Option<String>,
}Error handling
use seev::SeevError;
match Seev::charge(request).await {
Ok(charge) => println!("Checkout URL: {}", charge.checkout_url),
Err(SeevError::Api { code, message }) => {
eprintln!("SDK error [{}]: {}", code, message);
}
Err(SeevError::Network(e)) => {
eprintln!("Network error: {}", e);
}
Err(e) => eprintln!("Unexpected error: {}", e),
}Common errors:
| Error | Cause |
|---|---|
"Seev SDK not initialised. Call init() first." | Seev::init() was not called before making a request |
"Failed to create checkout session: ..." | Bad request data or invalid API keys |
"Failed to verify checkout session: session not found or invalid" | Expired or invalid session ID |
"Invalid webhook payload: missing event type" | Malformed webhook body |
"Network error: ..." | Connectivity issue or unreachable endpoint |
Full integration example
use axum::{
extract::{Json as BodyJson, Path, Query},
response::Json,
routing::{get, post},
Router,
};
use seev::{ChargeRequest, CheckoutItem, CheckoutRecipient, Seev};
use serde_json::Value;
use std::collections::HashMap;
#[tokio::main]
async fn main() {
Seev::init(seev::Config {
public_key: std::env::var("SEEV_PUBLIC_KEY").unwrap(),
private_key: std::env::var("SEEV_PRIVATE_KEY").unwrap(),
..Default::default()
})
.expect("Failed to initialise Seev SDK");
let app = Router::new()
.route("/checkout", post(create_checkout))
.route("/callback", get(handle_callback))
.route("/webhooks/seev", post(handle_webhook))
.route("/transaction/:id", get(get_transaction));
let listener = tokio::net::TcpListener::bind("0.0.0.0:8080").await.unwrap();
axum::serve(listener, app).await.unwrap();
}
async fn create_checkout() -> Json<Value> {
let charge = Seev::charge(ChargeRequest {
charge_type: "checkout".to_string(),
recipient: CheckoutRecipient {
name: "Jane Doe".to_string(),
email: "jane@example.com".to_string(),
..Default::default()
},
items: Some(vec![CheckoutItem {
name: "Widget".to_string(),
price: 2500,
quantity: Some(1),
..Default::default()
}]),
currency: "GHS".to_string(),
redirect_url: "https://myapp.com/callback".to_string(),
..Default::default()
})
.await
.unwrap();
Json(serde_json::json!({ "checkout_url": charge.checkout_url }))
}
async fn handle_callback(Query(params): Query<HashMap<String, String>>) -> Json<Value> {
let session_id = params.get("session_id").cloned().unwrap_or_default();
let result = Seev::callback(&session_id).await.unwrap();
if result.is_successful() {
Json(serde_json::json!({ "status": "success", "session_id": result.id }))
} else {
Json(serde_json::json!({ "status": result.status }))
}
}
async fn handle_webhook(BodyJson(body): BodyJson<Value>) -> Json<Value> {
let event = Seev::webhook(body, None).await.unwrap();
if event.is("checkout.completed") {
println!("Payment completed: {:?}", event.get_data());
}
Json(serde_json::json!({ "received": true }))
}
async fn get_transaction(Path(id): Path<String>) -> Json<Value> {
let txn = Seev::transaction(&id).await.unwrap();
Json(serde_json::json!({
"id": txn.id,
"status": txn.status,
"amount": txn.amount,
"currency": txn.currency,
}))
}