# Shopify Rust Client
A Rust client library for interacting with the Shopify Admin API. Supports both REST and GraphQL APIs with a focus on app development and order management.
## Features
### Current Support
#### Core APIs
- 📦 **Orders** (REST): Get orders by ID/name, update order properties
- 💳 **Subscriptions** (GraphQL): Recurring/usage/combined subscriptions, trial management, usage tracking
- 🎁 **Discounts** (GraphQL): Create and manage automatic app discounts
- ⚙️ **App Installation** (GraphQL): App metadata, metafields management
- 🛒 **Cart Transform** (GraphQL): Create cart transformations
- 🔧 **Shopify Functions** (GraphQL): List available functions
#### Developer Experience
- 📦 **Type-Safe**: Strongly typed models for all API responses
- 🚀 **Async/Await**: Built on `reqwest` for asynchronous HTTP requests
- 🔐 **Secure**: Token-based authentication support
- 🪝 **Webhook Support**: Parse customer and shop compliance webhooks
- 🔍 **Request Callbacks**: Optional before/after hooks for observability and logging
- 🎯 **Client-Based**: Single client instance with organized service modules
### Roadmap
- 🚧 **Products API**: Product management and variants
- 🚧 **Customers API**: Customer management and search
- 🚧 **Inventory API**: Inventory tracking and locations
- 🚧 **Fulfillments API**: Order fulfillment operations
- 🚧 **Additional Webhooks**: Support for more webhook topics beyond compliance
- 🚧 **Rate Limiting**: Built-in request throttling and retry logic
## Installation
Add this to your `Cargo.toml`:
```toml
[dependencies]
shopify-client = "0.1.0"
```
Or install directly:
```bash
cargo add shopify-client
```
## Public API
The library exposes the following public modules:
- **`ShopifyClient`** - Main client for making API calls
- **`types`** - All type definitions organized by resource (e.g., `types::order`, `types::subscription`)
- **`webhooks`** - Webhook parsing utilities and types
- **Callback Types** - `BeforeRequestCallback`, `AfterRequestCallback`, `RequestCallbacks`
```rust
use shopify_client::ShopifyClient; // Main client
use shopify_client::types::order::*; // Order types
use shopify_client::types::subscription::*; // Subscription types
use shopify_client::webhooks::*; // Webhook utilities
use shopify_client::{BeforeRequestCallback, AfterRequestCallback}; // Callback types
```
## Usage
### Initialize the Client
```rust
use shopify_client::ShopifyClient;
let client = ShopifyClient::new(
"https://your-shop.myshopify.com".to_string(),
"your-access-token".to_string(),
None, // Optional API version, defaults to "2026-01"
);
```
### Initialize with Request Callbacks
```rust
use shopify_client::ShopifyClient;
use std::sync::Arc;
println!("← Response from {} ({} bytes)", url, response.len());
});
let client = ShopifyClient::new_with_callbacks(
"https://your-shop.myshopify.com".to_string(),
"your-access-token".to_string(),
None,
Some(before_request),
Some(after_request),
);
```
### Get Order by ID
```rust
use shopify_client::ShopifyClient;
#[tokio::main]
async fn main() {
let client = ShopifyClient::new(
"https://your-shop.myshopify.com".to_string(),
"your-access-token".to_string(),
None,
);
let order_id = "1234567890".to_string();
match client.order.get_with_id(&order_id).await {
Ok(response) => {
println!("Order: {:?}", response.order);
}
Err(e) => {
eprintln!("Error: {:?}", e);
}
}
}
```
### Get Order by Name
```rust
use shopify_client::ShopifyClient;
#[tokio::main]
async fn main() {
let client = ShopifyClient::new(
"https://your-shop.myshopify.com".to_string(),
"your-access-token".to_string(),
None,
);
let order_name = "1001".to_string(); // Without the # prefix
match client.order.get_with_name(&order_name).await {
Ok(response) => {
println!("Orders found: {}", response.orders.len());
for order in response.orders {
println!("Order {}: {}", order.name, order.id);
}
}
Err(e) => {
eprintln!("Error: {:?}", e);
}
}
}
```
### Update Order (Patch)
```rust
use shopify_client::ShopifyClient;
use shopify_client::types::order::{PatchOrderRequest, PatchOrder};
#[tokio::main]
async fn main() {
let client = ShopifyClient::new(
"https://your-shop.myshopify.com".to_string(),
"your-access-token".to_string(),
None,
);
let order_id = "1234567890".to_string();
let patch_request = PatchOrderRequest {
order: PatchOrder {
tags: vec!["processed".to_string(), "priority".to_string()],
},
};
match client.order.patch(&order_id, &patch_request).await {
Ok(response) => {
println!("Order updated: {:?}", response.order);
}
Err(e) => {
eprintln!("Error: {:?}", e);
}
}
}
```
### Create Recurring Subscription
```rust
use shopify_client::ShopifyClient;
use shopify_client::types::subscription::{CreateRecurringSubscriptionRequest, AppPricingInterval};
#[tokio::main]
async fn main() {
let client = ShopifyClient::new(
"https://your-shop.myshopify.com".to_string(),
"your-access-token".to_string(),
None,
);
let request = CreateRecurringSubscriptionRequest {
name: "Premium Plan".to_string(),
price: 29.99,
currency_code: "USD".to_string(),
return_url: "https://your-app.com/billing".to_string(),
interval: Some(AppPricingInterval::Every30Days),
trial_days: Some(7),
test: Some(true),
discount: None,
};
match client.subscription.create_recurring(&request).await {
Ok(response) => {
println!("Subscription created: {:?}", response);
println!("Confirmation URL: {}", response.confirmation_url);
}
Err(e) => {
eprintln!("Error: {:?}", e);
}
}
}
```
### Create Automatic App Discount
```rust
use shopify_client::ShopifyClient;
use shopify_client::types::discount::DiscountAutomaticAppInput;
#[tokio::main]
async fn main() {
let client = ShopifyClient::new(
"https://your-shop.myshopify.com".to_string(),
"your-access-token".to_string(),
None,
);
let input = DiscountAutomaticAppInput {
title: "Summer Sale".to_string(),
function_handle: "my-discount-function".to_string(),
starts_at: "2024-06-01T00:00:00Z".to_string(),
ends_at: None,
combines_with: None,
discount_classes: None,
context: None,
metafields: None,
applies_on_subscription: None,
applies_on_one_time_purchase: None,
recurring_cycle_limit: None,
};
match client.discount.create_automatic_app_discount(&input).await {
Ok(response) => {
println!("Discount created: {:?}", response);
}
Err(e) => {
eprintln!("Error: {:?}", e);
}
}
}
```
### Parse Webhooks
```rust
use shopify_client::webhooks::{parse_webhook_with_header, WebhookPayload};
fn handle_webhook(topic_header: &str, payload: &str) {
match parse_webhook_with_header(topic_header, payload) {
Ok(WebhookPayload::CustomersDataRequest(data)) => {
println!("Customer data request for shop: {}", data.shop_domain);
println!("Customer: {:?}", data.customer);
println!("Orders requested: {:?}", data.orders_requested);
}
Ok(WebhookPayload::CustomersRedact(data)) => {
println!("Customer redact request for shop: {}", data.shop_domain);
println!("Customer to redact: {:?}", data.customer);
println!("Orders to redact: {:?}", data.orders_to_redact);
}
Ok(WebhookPayload::ShopRedact(data)) => {
println!("Shop redact request for shop: {}", data.shop_domain);
println!("Shop ID: {}", data.shop_id);
}
Err(e) => {
eprintln!("Failed to parse webhook: {:?}", e);
}
}
}
```
## Project Structure
The library is organized into a modular structure for easy extensibility:
```
src/
├── lib.rs # Main client entry point and exports
├── types/ # Public type definitions
│ ├── mod.rs # Type module exports
│ ├── order.rs # Order types (REST)
│ ├── subscription.rs # Subscription types (GraphQL)
│ ├── discount.rs # Discount types (GraphQL)
│ ├── app_installation.rs # App installation types (GraphQL)
│ ├── cart_transform.rs # Cart transform types (GraphQL)
│ └── shopify_functions.rs # Shopify functions types (GraphQL)
├── webhooks/ # Public webhook parsing module
│ ├── mod.rs # Webhook parsing functions
│ └── types.rs # Webhook payload types
├── common/ # Internal shared utilities (private)
│ ├── mod.rs
│ ├── types.rs # Common types (APIError, RequestCallbacks)
│ ├── utils.rs # Utility functions
│ └── http.rs # Centralized GraphQL execution
└── services/ # Internal API services (private)
├── order/ # Order service (REST)
├── subscription/ # Subscription service (GraphQL)
├── discount/ # Discount service (GraphQL)
├── app_installation/ # App installation service (GraphQL)
├── cart_transform/ # Cart transform service (GraphQL)
└── shopify_functions/ # Shopify functions service (GraphQL)
├── mod.rs # Service struct with public methods
└── remote.rs # Internal API implementation
```
### Design Philosophy
- **Client-Based**: Initialize a `ShopifyClient` once and access services through it
- **Clean Public API**: Three public modules - `ShopifyClient`, `types`, and `webhooks`
- **Encapsulation**: All internal implementation (`common`, `services`) is private
- **Service-Oriented**: Each API resource (orders, products, etc.) is its own service module
- **Clear Separation**: Services for outgoing API calls, webhooks for incoming data parsing
- **Type Safety**: All API responses are strongly typed with comprehensive models
## Data Models
The library provides strongly-typed models organized by resource:
### Order Types (`types::order`)
- **Order**: Complete order information including customer, line items, fulfillments, pricing, and timestamps
- **Customer**: Customer details (ID, email, phone, name, addresses)
- **LineItem**: Product line items with quantities, prices, properties, and fulfillment status
- **OrderFulfillment**: Fulfillment tracking with tracking numbers, URLs, and shipment status
- **Address**: Billing and shipping address information
- **PriceSet**: Multi-currency pricing with shop and presentment money
- **Property**: Custom line item properties
- **PatchOrderRequest** / **PatchOrder**: Request types for updating orders
### Subscription Types (`types::subscription`)
- **CreateRecurringSubscriptionRequest**: Create recurring app subscriptions
- **CreateUsageSubscriptionRequest**: Create usage-based subscriptions
- **CreateCombinedSubscriptionRequest**: Create subscriptions with both recurring and usage pricing
- **CreateUsageRecordRequest**: Record usage for usage-based subscriptions
- **AppPricingInterval**: Subscription billing intervals (Every30Days, Annual)
- **CreateSubscriptionResp**: Subscription creation response with confirmation URL
- **ActiveSubscriptionsResp**: List of active app subscriptions
### Discount Types (`types::discount`)
- **DiscountAutomaticAppInput**: Create automatic app discounts
- **DiscountAutomaticAppUpdateInput**: Update existing automatic app discounts
- **DiscountAutomaticAppCreateResp**: Discount creation response
- **DiscountNodesResp**: List discounts with pagination support
### App Installation Types (`types::app_installation`)
- **GetCurrentAppInstallationResp**: Current app installation details
- **MetafieldInput**: Input for creating/updating metafields
- **SetMetafieldsResp**: Metafield operation response
- **GetMetafieldResp** / **ListMetafieldsResp**: Metafield retrieval responses
### Cart Transform Types (`types::cart_transform`)
- **CartTransformCreateInput**: Create cart transformation functions
- **CartTransformCreateResp**: Cart transform creation response
### Shopify Functions Types (`types::shopify_functions`)
- **ShopifyFunctionsResp**: List of available Shopify Functions
### Webhook Types (`webhooks::types`)
- **CustomersDataRequestPayload**: Customer data request webhook payload
- **CustomersRedactPayload**: Customer redaction webhook payload
- **ShopRedactPayload**: Shop redaction webhook payload
- **WebhookPayload**: Enum containing all webhook payload types
- **WebhookParseError**: Error type for webhook parsing failures
- **WebhookCustomer**: Customer information in webhook payloads
## Error Handling
The library uses a custom `APIError` enum for comprehensive error handling:
```rust
pub enum APIError {
ServerError { errors: String },
FailedToParse,
NetworkError,
}
```
Errors are returned through `Result<T, APIError>` for easy error propagation and handling.
## Authentication
This client requires a Shopify Admin API access token. You can obtain one by:
1. Creating a custom app in your Shopify admin panel
2. Generating an Admin API access token
3. Granting the necessary permissions (e.g., `read_orders`, `write_orders`)
## Request Callbacks
The library supports optional before/after request callbacks for logging, monitoring, and observability:
```rust
use shopify_client::ShopifyClient;
use std::sync::Arc;
if let Some(body) = body {
println!("Request body: {}", body);
}
});
println!("Response size: {} bytes", response_body.len());
});
let client = ShopifyClient::new_with_callbacks(
"https://your-shop.myshopify.com".to_string(),
"your-access-token".to_string(),
None,
Some(before),
Some(after),
);
```
**Features:**
- Callbacks are synchronous closures that fire before and after every request
- Access token is NOT passed to callbacks for security
- Callbacks use panic catching to prevent user errors from crashing requests
- Works for both REST and GraphQL requests
- Optional - use `new()` for no callbacks, `new_with_callbacks()` for callbacks
## API Version
Currently uses Shopify Admin API version **2026-01**.
## Requirements
- Rust 1.56 or later (2021 edition)
- Dependencies:
- `reqwest` (with JSON support)
- `serde` (with derive feature)
- `serde_json`
## Contributing
Contributions are welcome! Please feel free to submit a Pull Request.
## License
This project is licensed under your preferred license.
## Disclaimer
This is an unofficial Shopify client library. For official SDKs and documentation, visit [Shopify's Developer Documentation](https://shopify.dev/).