> For the complete documentation index, see [llms.txt](https://docs.gameket.io/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.gameket.io/examples/rust.md).

# Rust

#### Basic Setup

Add dependencies to `Cargo.toml`:

```toml
[dependencies]
reqwest = { version = "0.11", features = ["json"] }
tokio = { version = "1", features = ["full"] }
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
chrono = { version = "0.4", features = ["serde"] }
```

#### Client Implementation

{% code expandable="true" %}

```rust
use reqwest::Client;
use serde::{Deserialize, Serialize};
use std::sync::Arc;
use tokio::sync::Mutex;
use chrono::{DateTime, Utc};

#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct TokenResponse {
    pub token: String,
    pub token_type: String,
    pub expires_in_seconds: u32,
    pub expires_at: DateTime<Utc>,
    pub merchant: MerchantInfo,
}

#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct MerchantInfo {
    pub store_id: String,
    pub store_name: String,
}

#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct ApiResponse<T> {
    pub success: bool,
    pub data: T,
}

#[derive(Serialize, Deserialize, Debug)]
pub struct Order {
    pub order_id: String,
    pub status: String,
    pub amount: f64,
}

pub struct GametekMerchantAPI {
    client: Client,
    api_key: String,
    secret: String,
    base_url: String,
    token: Arc<Mutex<Option<TokenResponse>>>,
}

impl GametekMerchantAPI {
    pub fn new(api_key: String, secret: String) -> Self {
        Self {
            client: Client::new(),
            api_key,
            secret,
            base_url: "https://api.gameket.io".to_string(),
            token: Arc::new(Mutex::new(None)),
        }
    }

    pub async fn get_token(&self) -> Result<String, Box<dyn std::error::Error>> {
        let mut token_guard = self.token.lock().await;

        // Return cached token if still valid
        if let Some(token_resp) = token_guard.as_ref() {
            let expires_at = token_resp.expires_at.timestamp();
            let now = Utc::now().timestamp();
            if now < expires_at - 60 {
                return Ok(token_resp.token.clone());
            }
        }

        // Request new token
        let response = self
            .client
            .post(format!("{}/merchant/auth/check", self.base_url))
            .json(&serde_json::json!({
                "apiKey": self.api_key,
                "secret": self.secret
            }))
            .send()
            .await?;

        let resp_data: ApiResponse<TokenResponse> = response.json().await?;
        let token_resp = resp_data.data;
        let token = token_resp.token.clone();

        *token_guard = Some(token_resp);
        Ok(token)
    }

    pub async fn get_profile(&self) -> Result<ApiResponse<Order>, Box<dyn std::error::Error>> {
        let token = self.get_token().await?;

        let response = self
            .client
            .get(format!("{}/merchant", self.base_url))
            .header("Authorization", format!("Bearer {}", token))
            .header("x-merchant-api-key", &self.api_key)
            .send()
            .await?;

        Ok(response.json().await?)
    }

    pub async fn list_orders(
        &self,
        page: u32,
        limit: u32,
        status: Option<&str>,
    ) -> Result<ApiResponse<Vec<Order>>, Box<dyn std::error::Error>> {
        let token = self.get_token().await?;

        let mut url = format!(
            "{}/merchant/orders?page={}&limit={}",
            self.base_url, page, limit
        );

        if let Some(s) = status {
            url.push_str(&format!("&status={}", s));
        }

        let response = self
            .client
            .get(url)
            .header("Authorization", format!("Bearer {}", token))
            .header("x-merchant-api-key", &self.api_key)
            .send()
            .await?;

        Ok(response.json().await?)
    }

    pub async fn deliver_codes(
        &self,
        order_id: &str,
        codes: Vec<String>,
    ) -> Result<ApiResponse<Order>, Box<dyn std::error::Error>> {
        let token = self.get_token().await?;

        let response = self
            .client
            .post(format!(
                "{}/merchant/orders/codes?orderId={}",
                self.base_url, order_id
            ))
            .header("Authorization", format!("Bearer {}", token))
            .header("x-merchant-api-key", &self.api_key)
            .header("Idempotency-Key", format!("deliver-{}", order_id))
            .json(&serde_json::json!({ "codes": codes }))
            .send()
            .await?;

        Ok(response.json().await?)
    }

    pub async fn refund_order(
        &self,
        order_id: &str,
        reason: &str,
        amount: f64,
    ) -> Result<ApiResponse<Order>, Box<dyn std::error::Error>> {
        let token = self.get_token().await?;

        let response = self
            .client
            .post(format!(
                "{}/merchant/orders/refund?orderId={}",
                self.base_url, order_id
            ))
            .header("Authorization", format!("Bearer {}", token))
            .header("x-merchant-api-key", &self.api_key)
            .header("Idempotency-Key", format!("refund-{}", order_id))
            .json(&serde_json::json!({
                "reason": reason,
                "amount": amount
            }))
            .send()
            .await?;

        Ok(response.json().await?)
    }
}

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let client = GametekMerchantAPI::new(
        std::env::var("MERCHANT_API_KEY")?,
        std::env::var("MERCHANT_SECRET")?,
    );

    // Get profile
    let profile = client.get_profile().await?;
    println!("Welcome {:?}", profile.data);

    // List orders
    let orders = client.list_orders(1, 50, Some("pending")).await?;
    println!("Found {} pending orders", orders.data.len());

    // Deliver codes for first order
    if let Some(order) = orders.data.first() {
        let codes = vec!["CODE-001".to_string(), "CODE-002".to_string()];
        let result = client.deliver_codes(&order.order_id, codes).await?;
        println!("Delivered codes to order {}", order.order_id);
    }

    Ok(())
}
```

{% endcode %}

#### Batch Process Orders (Rust with Tokio)

{% code expandable="true" %}

```rust
use futures::stream::{self, StreamExt};

pub async fn process_pending_orders(
    client: &GametekMerchantAPI,
) -> Result<(), Box<dyn std::error::Error>> {
    let mut page = 1;
    let mut has_more = true;

    while has_more {
        let response = client.list_orders(page, 100, Some("pending")).await?;
        let orders = &response.data;

        // Process orders concurrently (max 10 at a time)
        stream::iter(orders.iter())
            .map(|order| async move {
                let codes = vec![
                    format!("GAMEKET-{}", uuid::Uuid::new_v4()),
                    format!("GAMEKET-{}", uuid::Uuid::new_v4()),
                ];

                match client.deliver_codes(&order.order_id, codes).await {
                    Ok(_) => println!("✓ Delivered codes for order {}", order.order_id),
                    Err(e) => {
                        eprintln!("✗ Failed to process order {}: {}", order.order_id, e)
                    }
                }
            })
            .buffer_unordered(10)
            .collect::<()>()
            .await;

        // Check if there are more pages
        has_more = page < 10; // Replace with actual page count from response
        page += 1;
    }

    Ok(())
}

// Run in background task
#[tokio::main]
async fn main() {
    let client = GametekMerchantAPI::new(
        std::env::var("MERCHANT_API_KEY").unwrap(),
        std::env::var("MERCHANT_SECRET").unwrap(),
    );

    // Run every hour
    loop {
        if let Err(e) = process_pending_orders(&client).await {
            eprintln!("Error processing orders: {}", e);
        }
        tokio::time::sleep(std::time::Duration::from_secs(3600)).await;
    }
}
```

{% endcode %}
