use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
use crate::clients::RestClient;
use crate::rest::{ResourceError, ResourceOperation, ResourcePath, RestResource};
use crate::HttpMethod;
use super::common::{Address, DiscountApplication, LineItem, NoteAttribute, ShippingLine, TaxLine};
use super::customer::Customer;
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq, Default)]
#[serde(rename_all = "snake_case")]
pub enum FinancialStatus {
#[default]
Pending,
Authorized,
PartiallyPaid,
Paid,
PartiallyRefunded,
Refunded,
Voided,
}
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "snake_case")]
pub enum FulfillmentStatus {
Fulfilled,
Partial,
Unfulfilled,
Restocked,
}
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "snake_case")]
pub enum CancelReason {
Customer,
Fraud,
Inventory,
Declined,
Other,
}
#[derive(Debug, Clone, Serialize, Deserialize, Default, PartialEq, Eq)]
pub struct DiscountCode {
#[serde(skip_serializing_if = "Option::is_none")]
pub code: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub amount: Option<String>,
#[serde(rename = "type", skip_serializing_if = "Option::is_none")]
pub discount_type: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize, Default, PartialEq, Eq)]
pub struct Refund {
#[serde(skip_serializing_if = "Option::is_none")]
pub id: Option<u64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub order_id: Option<u64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub created_at: Option<DateTime<Utc>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub note: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub user_id: Option<u64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub processed_at: Option<DateTime<Utc>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub restock: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
pub duties: Option<serde_json::Value>,
#[serde(skip_serializing_if = "Option::is_none")]
pub refund_duties: Option<serde_json::Value>,
#[serde(skip_serializing_if = "Option::is_none")]
pub refund_line_items: Option<serde_json::Value>,
#[serde(skip_serializing_if = "Option::is_none")]
pub transactions: Option<serde_json::Value>,
#[serde(skip_serializing_if = "Option::is_none")]
pub order_adjustments: Option<serde_json::Value>,
#[serde(skip_serializing_if = "Option::is_none")]
pub admin_graphql_api_id: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize, Default, PartialEq, Eq)]
pub struct OrderFulfillment {
#[serde(skip_serializing_if = "Option::is_none")]
pub id: Option<u64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub order_id: Option<u64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub status: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub created_at: Option<DateTime<Utc>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub service: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub updated_at: Option<DateTime<Utc>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub tracking_company: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub shipment_status: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub location_id: Option<u64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub tracking_number: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub tracking_numbers: Option<Vec<String>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub tracking_url: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub tracking_urls: Option<Vec<String>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub line_items: Option<serde_json::Value>,
#[serde(skip_serializing_if = "Option::is_none")]
pub admin_graphql_api_id: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize, Default, PartialEq)]
pub struct Order {
#[serde(skip_serializing)]
pub id: Option<u64>,
#[serde(skip_serializing)]
pub name: Option<String>,
#[serde(skip_serializing)]
pub order_number: Option<u64>,
#[serde(skip_serializing)]
pub created_at: Option<DateTime<Utc>>,
#[serde(skip_serializing)]
pub updated_at: Option<DateTime<Utc>>,
#[serde(skip_serializing)]
pub confirmation_number: Option<String>,
#[serde(skip_serializing)]
pub admin_graphql_api_id: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub email: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub phone: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub closed_at: Option<DateTime<Utc>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub cancelled_at: Option<DateTime<Utc>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub cancel_reason: Option<CancelReason>,
#[serde(skip_serializing_if = "Option::is_none")]
pub financial_status: Option<FinancialStatus>,
#[serde(skip_serializing_if = "Option::is_none")]
pub fulfillment_status: Option<FulfillmentStatus>,
#[serde(skip_serializing_if = "Option::is_none")]
pub currency: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub total_price: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub subtotal_price: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub total_tax: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub total_discounts: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub total_weight: Option<i64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub taxes_included: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
pub buyer_accepts_marketing: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
pub note: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub note_attributes: Option<Vec<NoteAttribute>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub tags: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub app_id: Option<u64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub browser_ip: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub customer_locale: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub order_status_url: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub processed_at: Option<DateTime<Utc>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub source_name: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub total_price_usd: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub total_shipping_price_set: Option<serde_json::Value>,
#[serde(skip_serializing_if = "Option::is_none")]
pub total_line_items_price: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub total_outstanding: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub current_total_price: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub current_subtotal_price: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub current_total_tax: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub current_total_discounts: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub confirmed: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
pub test: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
pub user_id: Option<u64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub location_id: Option<u64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub source_identifier: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub source_url: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub device_id: Option<u64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub landing_site: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub referring_site: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub gateway: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub payment_gateway_names: Option<Vec<String>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub processing_method: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub reference: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub checkout_id: Option<u64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub checkout_token: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub cart_token: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub token: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub line_items: Option<Vec<LineItem>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub billing_address: Option<Address>,
#[serde(skip_serializing_if = "Option::is_none")]
pub shipping_address: Option<Address>,
#[serde(skip_serializing_if = "Option::is_none")]
pub tax_lines: Option<Vec<TaxLine>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub discount_codes: Option<Vec<DiscountCode>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub discount_applications: Option<Vec<DiscountApplication>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub shipping_lines: Option<Vec<ShippingLine>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub fulfillments: Option<Vec<OrderFulfillment>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub refunds: Option<Vec<Refund>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub customer: Option<Customer>,
#[serde(skip_serializing_if = "Option::is_none")]
pub client_details: Option<serde_json::Value>,
#[serde(skip_serializing_if = "Option::is_none")]
pub payment_details: Option<serde_json::Value>,
}
impl RestResource for Order {
type Id = u64;
type FindParams = OrderFindParams;
type AllParams = OrderListParams;
type CountParams = OrderCountParams;
const NAME: &'static str = "Order";
const PLURAL: &'static str = "orders";
const PATHS: &'static [ResourcePath] = &[
ResourcePath::new(
HttpMethod::Get,
ResourceOperation::Find,
&["id"],
"orders/{id}",
),
ResourcePath::new(HttpMethod::Get, ResourceOperation::All, &[], "orders"),
ResourcePath::new(
HttpMethod::Get,
ResourceOperation::Count,
&[],
"orders/count",
),
ResourcePath::new(HttpMethod::Post, ResourceOperation::Create, &[], "orders"),
ResourcePath::new(
HttpMethod::Put,
ResourceOperation::Update,
&["id"],
"orders/{id}",
),
ResourcePath::new(
HttpMethod::Delete,
ResourceOperation::Delete,
&["id"],
"orders/{id}",
),
];
fn get_id(&self) -> Option<Self::Id> {
self.id
}
}
impl Order {
pub async fn cancel(&self, client: &RestClient) -> Result<Self, ResourceError> {
let id = self.get_id().ok_or(ResourceError::PathResolutionFailed {
resource: Self::NAME,
operation: "cancel",
})?;
let path = format!("orders/{id}/cancel");
let body = serde_json::json!({});
let response = client.post(&path, body, None).await?;
if !response.is_ok() {
return Err(ResourceError::from_http_response(
response.code,
&response.body,
Self::NAME,
Some(&id.to_string()),
response.request_id(),
));
}
let order: Self = response
.body
.get("order")
.ok_or_else(|| {
ResourceError::Http(crate::clients::HttpError::Response(
crate::clients::HttpResponseError {
code: response.code,
message: "Missing 'order' in response".to_string(),
error_reference: response.request_id().map(ToString::to_string),
},
))
})
.and_then(|v| {
serde_json::from_value(v.clone()).map_err(|e| {
ResourceError::Http(crate::clients::HttpError::Response(
crate::clients::HttpResponseError {
code: response.code,
message: format!("Failed to deserialize order: {e}"),
error_reference: response.request_id().map(ToString::to_string),
},
))
})
})?;
Ok(order)
}
pub async fn close(&self, client: &RestClient) -> Result<Self, ResourceError> {
let id = self.get_id().ok_or(ResourceError::PathResolutionFailed {
resource: Self::NAME,
operation: "close",
})?;
let path = format!("orders/{id}/close");
let body = serde_json::json!({});
let response = client.post(&path, body, None).await?;
if !response.is_ok() {
return Err(ResourceError::from_http_response(
response.code,
&response.body,
Self::NAME,
Some(&id.to_string()),
response.request_id(),
));
}
let order: Self = response
.body
.get("order")
.ok_or_else(|| {
ResourceError::Http(crate::clients::HttpError::Response(
crate::clients::HttpResponseError {
code: response.code,
message: "Missing 'order' in response".to_string(),
error_reference: response.request_id().map(ToString::to_string),
},
))
})
.and_then(|v| {
serde_json::from_value(v.clone()).map_err(|e| {
ResourceError::Http(crate::clients::HttpError::Response(
crate::clients::HttpResponseError {
code: response.code,
message: format!("Failed to deserialize order: {e}"),
error_reference: response.request_id().map(ToString::to_string),
},
))
})
})?;
Ok(order)
}
pub async fn open(&self, client: &RestClient) -> Result<Self, ResourceError> {
let id = self.get_id().ok_or(ResourceError::PathResolutionFailed {
resource: Self::NAME,
operation: "open",
})?;
let path = format!("orders/{id}/open");
let body = serde_json::json!({});
let response = client.post(&path, body, None).await?;
if !response.is_ok() {
return Err(ResourceError::from_http_response(
response.code,
&response.body,
Self::NAME,
Some(&id.to_string()),
response.request_id(),
));
}
let order: Self = response
.body
.get("order")
.ok_or_else(|| {
ResourceError::Http(crate::clients::HttpError::Response(
crate::clients::HttpResponseError {
code: response.code,
message: "Missing 'order' in response".to_string(),
error_reference: response.request_id().map(ToString::to_string),
},
))
})
.and_then(|v| {
serde_json::from_value(v.clone()).map_err(|e| {
ResourceError::Http(crate::clients::HttpError::Response(
crate::clients::HttpResponseError {
code: response.code,
message: format!("Failed to deserialize order: {e}"),
error_reference: response.request_id().map(ToString::to_string),
},
))
})
})?;
Ok(order)
}
}
#[derive(Debug, Clone, Serialize, Deserialize, Default, PartialEq, Eq)]
pub struct OrderFindParams {
#[serde(skip_serializing_if = "Option::is_none")]
pub fields: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize, Default, PartialEq, Eq)]
pub struct OrderListParams {
#[serde(skip_serializing_if = "Option::is_none")]
pub ids: Option<Vec<u64>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub limit: Option<u32>,
#[serde(skip_serializing_if = "Option::is_none")]
pub since_id: Option<u64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub status: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub financial_status: Option<FinancialStatus>,
#[serde(skip_serializing_if = "Option::is_none")]
pub fulfillment_status: Option<FulfillmentStatus>,
#[serde(skip_serializing_if = "Option::is_none")]
pub created_at_min: Option<DateTime<Utc>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub created_at_max: Option<DateTime<Utc>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub updated_at_min: Option<DateTime<Utc>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub updated_at_max: Option<DateTime<Utc>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub processed_at_min: Option<DateTime<Utc>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub processed_at_max: Option<DateTime<Utc>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub attribution_app_id: Option<u64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub fields: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub page_info: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize, Default, PartialEq, Eq)]
pub struct OrderCountParams {
#[serde(skip_serializing_if = "Option::is_none")]
pub status: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub financial_status: Option<FinancialStatus>,
#[serde(skip_serializing_if = "Option::is_none")]
pub fulfillment_status: Option<FulfillmentStatus>,
#[serde(skip_serializing_if = "Option::is_none")]
pub created_at_min: Option<DateTime<Utc>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub created_at_max: Option<DateTime<Utc>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub updated_at_min: Option<DateTime<Utc>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub updated_at_max: Option<DateTime<Utc>>,
}
#[cfg(test)]
mod tests {
use super::*;
use crate::rest::{get_path, ResourceOperation};
#[test]
fn test_order_struct_serialization() {
let order = Order {
id: Some(450789469),
name: Some("#1001".to_string()),
email: Some("customer@example.com".to_string()),
phone: Some("+1-555-555-5555".to_string()),
total_price: Some("199.99".to_string()),
subtotal_price: Some("179.99".to_string()),
total_tax: Some("15.00".to_string()),
total_discounts: Some("5.00".to_string()),
currency: Some("USD".to_string()),
financial_status: Some(FinancialStatus::Paid),
fulfillment_status: Some(FulfillmentStatus::Unfulfilled),
tags: Some("important, vip".to_string()),
note: Some("Please gift wrap".to_string()),
buyer_accepts_marketing: Some(true),
..Default::default()
};
let json = serde_json::to_string(&order).unwrap();
let parsed: serde_json::Value = serde_json::from_str(&json).unwrap();
assert_eq!(parsed["email"], "customer@example.com");
assert_eq!(parsed["phone"], "+1-555-555-5555");
assert_eq!(parsed["total_price"], "199.99");
assert_eq!(parsed["currency"], "USD");
assert_eq!(parsed["financial_status"], "paid");
assert_eq!(parsed["fulfillment_status"], "unfulfilled");
assert_eq!(parsed["note"], "Please gift wrap");
assert!(parsed.get("id").is_none());
assert!(parsed.get("name").is_none());
}
#[test]
fn test_order_deserialization_with_nested_line_items() {
let json_str = r##"{
"id": 450789469,
"name": "#1001",
"email": "customer@example.com",
"order_number": 1001,
"total_price": "199.99",
"financial_status": "paid",
"fulfillment_status": "partial",
"line_items": [
{
"id": 669751112,
"variant_id": 457924702,
"product_id": 632910392,
"title": "IPod Nano - 8GB",
"quantity": 1,
"price": "199.00",
"sku": "IPOD2008BLACK",
"taxable": true,
"tax_lines": [
{
"title": "State Tax",
"price": "15.99",
"rate": 0.08
}
]
}
],
"billing_address": {
"first_name": "John",
"last_name": "Doe",
"address1": "123 Main St",
"city": "New York",
"province": "New York",
"country": "United States",
"zip": "10001"
},
"customer": {
"id": 207119551,
"email": "customer@example.com",
"first_name": "John",
"last_name": "Doe"
}
}"##;
let order: Order = serde_json::from_str(json_str).unwrap();
assert_eq!(order.id, Some(450789469));
assert_eq!(order.name.as_deref(), Some("#1001"));
assert_eq!(order.email.as_deref(), Some("customer@example.com"));
assert_eq!(order.order_number, Some(1001));
assert_eq!(order.financial_status, Some(FinancialStatus::Paid));
assert_eq!(order.fulfillment_status, Some(FulfillmentStatus::Partial));
let line_items = order.line_items.unwrap();
assert_eq!(line_items.len(), 1);
assert_eq!(line_items[0].id, Some(669751112));
assert_eq!(line_items[0].title.as_deref(), Some("IPod Nano - 8GB"));
assert_eq!(line_items[0].quantity, Some(1));
let tax_lines = line_items[0].tax_lines.as_ref().unwrap();
assert_eq!(tax_lines.len(), 1);
assert_eq!(tax_lines[0].title.as_deref(), Some("State Tax"));
let billing = order.billing_address.unwrap();
assert_eq!(billing.first_name.as_deref(), Some("John"));
assert_eq!(billing.city.as_deref(), Some("New York"));
let customer = order.customer.unwrap();
assert_eq!(customer.id, Some(207119551));
assert_eq!(customer.first_name.as_deref(), Some("John"));
}
#[test]
fn test_financial_status_enum_serialization() {
assert_eq!(
serde_json::to_string(&FinancialStatus::Pending).unwrap(),
"\"pending\""
);
assert_eq!(
serde_json::to_string(&FinancialStatus::Authorized).unwrap(),
"\"authorized\""
);
assert_eq!(
serde_json::to_string(&FinancialStatus::PartiallyPaid).unwrap(),
"\"partially_paid\""
);
assert_eq!(
serde_json::to_string(&FinancialStatus::Paid).unwrap(),
"\"paid\""
);
assert_eq!(
serde_json::to_string(&FinancialStatus::PartiallyRefunded).unwrap(),
"\"partially_refunded\""
);
assert_eq!(
serde_json::to_string(&FinancialStatus::Refunded).unwrap(),
"\"refunded\""
);
assert_eq!(
serde_json::to_string(&FinancialStatus::Voided).unwrap(),
"\"voided\""
);
let paid: FinancialStatus = serde_json::from_str("\"paid\"").unwrap();
let partially_refunded: FinancialStatus =
serde_json::from_str("\"partially_refunded\"").unwrap();
assert_eq!(paid, FinancialStatus::Paid);
assert_eq!(partially_refunded, FinancialStatus::PartiallyRefunded);
assert_eq!(FinancialStatus::default(), FinancialStatus::Pending);
}
#[test]
fn test_fulfillment_status_enum_serialization() {
assert_eq!(
serde_json::to_string(&FulfillmentStatus::Fulfilled).unwrap(),
"\"fulfilled\""
);
assert_eq!(
serde_json::to_string(&FulfillmentStatus::Partial).unwrap(),
"\"partial\""
);
assert_eq!(
serde_json::to_string(&FulfillmentStatus::Unfulfilled).unwrap(),
"\"unfulfilled\""
);
assert_eq!(
serde_json::to_string(&FulfillmentStatus::Restocked).unwrap(),
"\"restocked\""
);
let fulfilled: FulfillmentStatus = serde_json::from_str("\"fulfilled\"").unwrap();
let partial: FulfillmentStatus = serde_json::from_str("\"partial\"").unwrap();
assert_eq!(fulfilled, FulfillmentStatus::Fulfilled);
assert_eq!(partial, FulfillmentStatus::Partial);
}
#[test]
fn test_cancel_reason_enum_serialization() {
assert_eq!(
serde_json::to_string(&CancelReason::Customer).unwrap(),
"\"customer\""
);
assert_eq!(
serde_json::to_string(&CancelReason::Fraud).unwrap(),
"\"fraud\""
);
assert_eq!(
serde_json::to_string(&CancelReason::Inventory).unwrap(),
"\"inventory\""
);
assert_eq!(
serde_json::to_string(&CancelReason::Declined).unwrap(),
"\"declined\""
);
assert_eq!(
serde_json::to_string(&CancelReason::Other).unwrap(),
"\"other\""
);
let fraud: CancelReason = serde_json::from_str("\"fraud\"").unwrap();
let inventory: CancelReason = serde_json::from_str("\"inventory\"").unwrap();
assert_eq!(fraud, CancelReason::Fraud);
assert_eq!(inventory, CancelReason::Inventory);
}
#[test]
fn test_order_list_params_with_status_filters() {
let created_at_min = DateTime::parse_from_rfc3339("2024-01-01T00:00:00Z")
.unwrap()
.with_timezone(&Utc);
let params = OrderListParams {
ids: Some(vec![123, 456, 789]),
limit: Some(50),
since_id: Some(100),
status: Some("open".to_string()),
financial_status: Some(FinancialStatus::Paid),
fulfillment_status: Some(FulfillmentStatus::Unfulfilled),
created_at_min: Some(created_at_min),
created_at_max: None,
updated_at_min: None,
updated_at_max: None,
processed_at_min: None,
processed_at_max: None,
attribution_app_id: Some(12345),
fields: Some("id,name,total_price".to_string()),
page_info: None,
};
let json = serde_json::to_value(¶ms).unwrap();
assert_eq!(json["ids"], serde_json::json!([123, 456, 789]));
assert_eq!(json["limit"], 50);
assert_eq!(json["since_id"], 100);
assert_eq!(json["status"], "open");
assert_eq!(json["financial_status"], "paid");
assert_eq!(json["fulfillment_status"], "unfulfilled");
assert_eq!(json["attribution_app_id"], 12345);
assert_eq!(json["fields"], "id,name,total_price");
assert!(json["created_at_min"].as_str().is_some());
let empty_params = OrderListParams::default();
let empty_json = serde_json::to_value(&empty_params).unwrap();
assert_eq!(empty_json, serde_json::json!({}));
}
#[test]
fn test_order_resource_specific_operations_signatures() {
fn _assert_cancel_signature<F, Fut>(f: F)
where
F: Fn(&Order, &RestClient) -> Fut,
Fut: std::future::Future<Output = Result<Order, ResourceError>>,
{
let _ = f;
}
fn _assert_close_signature<F, Fut>(f: F)
where
F: Fn(&Order, &RestClient) -> Fut,
Fut: std::future::Future<Output = Result<Order, ResourceError>>,
{
let _ = f;
}
fn _assert_open_signature<F, Fut>(f: F)
where
F: Fn(&Order, &RestClient) -> Fut,
Fut: std::future::Future<Output = Result<Order, ResourceError>>,
{
let _ = f;
}
let order_without_id = Order::default();
assert!(order_without_id.get_id().is_none());
}
#[test]
fn test_order_get_id_returns_correct_value() {
let order_with_id = Order {
id: Some(450789469),
name: Some("#1001".to_string()),
..Default::default()
};
assert_eq!(order_with_id.get_id(), Some(450789469));
let order_without_id = Order {
id: None,
email: Some("new@example.com".to_string()),
..Default::default()
};
assert_eq!(order_without_id.get_id(), None);
}
#[test]
fn test_order_path_constants_are_correct() {
let find_path = get_path(Order::PATHS, ResourceOperation::Find, &["id"]);
assert!(find_path.is_some());
assert_eq!(find_path.unwrap().template, "orders/{id}");
assert_eq!(find_path.unwrap().http_method, HttpMethod::Get);
let all_path = get_path(Order::PATHS, ResourceOperation::All, &[]);
assert!(all_path.is_some());
assert_eq!(all_path.unwrap().template, "orders");
let count_path = get_path(Order::PATHS, ResourceOperation::Count, &[]);
assert!(count_path.is_some());
assert_eq!(count_path.unwrap().template, "orders/count");
let create_path = get_path(Order::PATHS, ResourceOperation::Create, &[]);
assert!(create_path.is_some());
assert_eq!(create_path.unwrap().http_method, HttpMethod::Post);
let update_path = get_path(Order::PATHS, ResourceOperation::Update, &["id"]);
assert!(update_path.is_some());
assert_eq!(update_path.unwrap().http_method, HttpMethod::Put);
let delete_path = get_path(Order::PATHS, ResourceOperation::Delete, &["id"]);
assert!(delete_path.is_some());
assert_eq!(delete_path.unwrap().http_method, HttpMethod::Delete);
assert_eq!(Order::NAME, "Order");
assert_eq!(Order::PLURAL, "orders");
}
#[test]
fn test_discount_code_serialization() {
let discount = DiscountCode {
code: Some("SAVE10".to_string()),
amount: Some("10.00".to_string()),
discount_type: Some("fixed_amount".to_string()),
};
let json = serde_json::to_string(&discount).unwrap();
let parsed: serde_json::Value = serde_json::from_str(&json).unwrap();
assert_eq!(parsed["code"], "SAVE10");
assert_eq!(parsed["amount"], "10.00");
assert_eq!(parsed["type"], "fixed_amount");
let json_str = r#"{"code":"SUMMER20","amount":"20.00","type":"percentage"}"#;
let parsed_discount: DiscountCode = serde_json::from_str(json_str).unwrap();
assert_eq!(parsed_discount.discount_type.as_deref(), Some("percentage"));
}
#[test]
fn test_refund_struct_serialization() {
let refund = Refund {
id: Some(123456),
order_id: Some(450789469),
note: Some("Customer requested".to_string()),
restock: Some(true),
..Default::default()
};
let json = serde_json::to_string(&refund).unwrap();
let parsed: serde_json::Value = serde_json::from_str(&json).unwrap();
assert_eq!(parsed["id"], 123456);
assert_eq!(parsed["order_id"], 450789469);
assert_eq!(parsed["note"], "Customer requested");
assert_eq!(parsed["restock"], true);
}
#[test]
fn test_order_with_all_nested_structures() {
let json_str = r##"{
"id": 450789469,
"name": "#1001",
"email": "customer@example.com",
"financial_status": "partially_refunded",
"line_items": [
{"id": 1, "title": "Product 1", "quantity": 2}
],
"billing_address": {
"first_name": "John",
"city": "New York"
},
"shipping_address": {
"first_name": "John",
"city": "New York"
},
"tax_lines": [
{"title": "Tax", "price": "10.00", "rate": 0.1}
],
"discount_codes": [
{"code": "SAVE10", "amount": "10.00", "type": "fixed_amount"}
],
"discount_applications": [
{"type": "discount_code", "value": "10.00", "code": "SAVE10"}
],
"shipping_lines": [
{"id": 1, "title": "Standard", "price": "5.00"}
],
"fulfillments": [
{"id": 1, "status": "success", "tracking_number": "1234"}
],
"refunds": [
{"id": 1, "note": "Partial refund"}
],
"customer": {
"id": 207119551,
"email": "customer@example.com"
}
}"##;
let order: Order = serde_json::from_str(json_str).unwrap();
assert!(order.line_items.is_some());
assert!(order.billing_address.is_some());
assert!(order.shipping_address.is_some());
assert!(order.tax_lines.is_some());
assert!(order.discount_codes.is_some());
assert!(order.discount_applications.is_some());
assert!(order.shipping_lines.is_some());
assert!(order.fulfillments.is_some());
assert!(order.refunds.is_some());
assert!(order.customer.is_some());
assert_eq!(
order.financial_status,
Some(FinancialStatus::PartiallyRefunded)
);
assert_eq!(
order.discount_codes.as_ref().unwrap()[0].code.as_deref(),
Some("SAVE10")
);
assert_eq!(
order.fulfillments.as_ref().unwrap()[0]
.tracking_number
.as_deref(),
Some("1234")
);
}
}