reevit 0.2.0

Official Rust SDK for the Reevit payments API
Documentation
use reevit::{
    Client, ConnectionListOptions, ConnectionRequest, CustomerListOptions, InvoiceListOptions,
    PaginationOptions, SubscriptionListOptions, WebhookEventListOptions,
};
use serde_json::json;
use wiremock::matchers::{method, path, query_param};
use wiremock::{Mock, MockServer, ResponseTemplate};

#[tokio::test]
async fn customer_lists_accept_wrapped_collection_responses() {
    let server = MockServer::start().await;
    Mock::given(method("GET"))
        .and(path("/v1/customers"))
        .and(query_param("limit", "25"))
        .and(query_param("search", "ama"))
        .respond_with(ResponseTemplate::new(200).set_body_json(json!({
            "customers": [{
                "id": "cus_123",
                "email": "ama@example.com",
                "name": "Ama"
            }]
        })))
        .mount(&server)
        .await;

    let client = Client::builder("pfk_test_example.secret", "org_123")
        .base_url(server.uri())
        .build()
        .unwrap();
    let customers = client
        .customers()
        .list(&CustomerListOptions {
            limit: Some(25),
            search: Some("ama".into()),
            ..CustomerListOptions::default()
        })
        .await
        .unwrap();

    assert_eq!(customers.len(), 1);
    assert_eq!(customers[0].id, "cus_123");
}

fn client(server: &MockServer) -> Client {
    Client::builder("pfk_test_example.secret", "org_123")
        .base_url(server.uri())
        .build()
        .unwrap()
}

#[tokio::test]
async fn connections_test_accepts_the_api_ok_response_shape() {
    let server = MockServer::start().await;
    Mock::given(method("POST"))
        .and(path("/v1/connections/test"))
        .respond_with(ResponseTemplate::new(200).set_body_json(json!({"ok": true})))
        .mount(&server)
        .await;

    let valid = client(&server)
        .connections()
        .test(
            &ConnectionRequest {
                provider: "paystack".into(),
                mode: "test".into(),
                credentials: Default::default(),
                ..ConnectionRequest::default()
            },
            Default::default(),
        )
        .await
        .unwrap();

    assert!(valid);
}

#[tokio::test]
async fn connections_list_all_follows_pagination_and_filters() {
    let server = MockServer::start().await;
    let filters = [
        ("provider", "paystack"),
        ("mode", "live"),
        ("status", "active"),
        ("label", "primary"),
        ("limit", "200"),
    ];

    let mut first = Mock::given(method("GET")).and(path("/v1/connections"));
    let mut second = Mock::given(method("GET")).and(path("/v1/connections"));
    for (key, value) in filters {
        first = first.and(query_param(key, value));
        second = second.and(query_param(key, value));
    }
    first
        .and(query_param("offset", "0"))
        .respond_with(ResponseTemplate::new(200).set_body_json(json!({
            "connections": [
                {"id": "conn_1", "provider": "paystack", "mode": "live", "status": "active",
                 "name_match_status": "match"},
                {"id": "conn_2", "provider": "paystack", "mode": "live", "status": "active"}
            ],
            "pagination": {"total": 3, "limit": 200, "offset": 0}
        })))
        .expect(1)
        .mount(&server)
        .await;
    second
        .and(query_param("offset", "2"))
        .respond_with(ResponseTemplate::new(200).set_body_json(json!({
            "connections": [
                {"id": "conn_3", "provider": "paystack", "mode": "live", "status": "active"}
            ],
            "pagination": {"total": 3, "limit": 200, "offset": 2}
        })))
        .expect(1)
        .mount(&server)
        .await;

    let connections = client(&server)
        .connections()
        .list_all(&ConnectionListOptions {
            provider: Some("paystack".into()),
            mode: Some("live".into()),
            status: Some("active".into()),
            label: Some("primary".into()),
            ..ConnectionListOptions::default()
        })
        .await
        .unwrap();

    assert_eq!(
        connections
            .iter()
            .map(|connection| connection.id.as_str())
            .collect::<Vec<_>>(),
        ["conn_1", "conn_2", "conn_3"]
    );
    assert_eq!(connections[0].name_match_status.as_deref(), Some("match"));
}

#[tokio::test]
async fn connections_list_labels_uses_the_labels_endpoint() {
    let server = MockServer::start().await;
    Mock::given(method("GET"))
        .and(path("/v1/connections/labels"))
        .respond_with(ResponseTemplate::new(200).set_body_json(json!([
            {"label": "primary", "total": 2}
        ])))
        .mount(&server)
        .await;

    let labels = client(&server).connections().list_labels().await.unwrap();

    assert_eq!(labels[0].label, "primary");
    assert_eq!(labels[0].total, 2);
}

#[tokio::test]
async fn subscriptions_list_uses_the_subscriptions_collection() {
    let server = MockServer::start().await;
    Mock::given(method("GET"))
        .and(path("/v1/subscriptions"))
        .respond_with(ResponseTemplate::new(200).set_body_json(json!({"subscriptions": []})))
        .mount(&server)
        .await;

    let subscriptions = client(&server)
        .subscriptions()
        .list(&SubscriptionListOptions::default())
        .await
        .unwrap();
    assert!(subscriptions.is_empty());
}

#[tokio::test]
async fn fraud_get_decodes_the_policy() {
    let server = MockServer::start().await;
    Mock::given(method("GET"))
        .and(path("/v1/policies/fraud"))
        .respond_with(ResponseTemplate::new(200).set_body_json(json!({
            "prefer": ["paystack"],
            "max_amount": 100000,
            "blocked_bins": [],
            "allowed_bins": [],
            "velocity_max_per_minute": 10
        })))
        .mount(&server)
        .await;

    let policy = client(&server).fraud().get().await.unwrap();
    assert_eq!(policy.prefer, ["paystack"]);
}

#[tokio::test]
async fn payment_links_resolve_public_codes() {
    let server = MockServer::start().await;
    Mock::given(method("GET"))
        .and(path("/v1/pay/summer-sale"))
        .respond_with(ResponseTemplate::new(200).set_body_json(json!({
            "id": "plink_123",
            "code": "summer-sale"
        })))
        .mount(&server)
        .await;

    let link = client(&server)
        .payment_links()
        .get_by_code("summer-sale")
        .await
        .unwrap();
    assert_eq!(link.id, "plink_123");
}

#[tokio::test]
async fn webhooks_list_uses_the_events_collection() {
    let server = MockServer::start().await;
    Mock::given(method("GET"))
        .and(path("/v1/webhooks/events"))
        .respond_with(ResponseTemplate::new(200).set_body_json(json!({"events": []})))
        .mount(&server)
        .await;

    let events = client(&server)
        .webhooks()
        .list_events(&WebhookEventListOptions::default())
        .await
        .unwrap();
    assert!(events.is_empty());
}

#[tokio::test]
async fn routing_rules_list_uses_the_rules_collection() {
    let server = MockServer::start().await;
    Mock::given(method("GET"))
        .and(path("/v1/routing-rules"))
        .respond_with(ResponseTemplate::new(200).set_body_json(json!({"rules": []})))
        .mount(&server)
        .await;

    let rules = client(&server)
        .routing_rules()
        .list(&PaginationOptions::default())
        .await
        .unwrap();
    assert!(rules.is_empty());
}

#[tokio::test]
async fn invoices_list_uses_the_invoices_collection() {
    let server = MockServer::start().await;
    Mock::given(method("GET"))
        .and(path("/v1/invoices"))
        .respond_with(ResponseTemplate::new(200).set_body_json(json!({"invoices": []})))
        .mount(&server)
        .await;

    let invoices = client(&server)
        .invoices()
        .list(&InvoiceListOptions::default())
        .await
        .unwrap();
    assert!(invoices.is_empty());
}