Generated using AI. Be aware that everything might not be accurate.



Orders and Fulfillment

Overview

For digital products, “fulfillment” is automatic—Etsy delivers the files immediately after purchase. But you still need to:

  • Track orders and sales
  • Handle customer inquiries
  • Process refunds when needed
  • Monitor download activity

This chapter covers accessing and managing order data via the API.

Understanding Etsy’s Order Structure

Etsy uses a hierarchy:

Receipt (Order)
└── Transactions (Line Items)
    ├── Transaction 1: Product A
    └── Transaction 2: Product B
  • Receipt: The complete order (may contain multiple items)
  • Transaction: A single line item (one product purchase)

Retrieving Orders

Get Recent Orders

def get_recent_orders(client, shop_id, limit=25):
    """Get the most recent orders."""
    response = client.get(
        f"/application/shops/{shop_id}/receipts",
        params={"limit": limit}
    )
    return response["results"]

Get Orders in Date Range

from datetime import datetime, timedelta

def get_orders_in_range(client, shop_id, start_date, end_date):
    """Get orders within a specific date range."""
    # Convert to Unix timestamps
    min_created = int(start_date.timestamp())
    max_created = int(end_date.timestamp())
    
    orders = []
    offset = 0
    
    while True:
        response = client.get(
            f"/application/shops/{shop_id}/receipts",
            params={
                "min_created": min_created,
                "max_created": max_created,
                "limit": 100,
                "offset": offset
            }
        )
        orders.extend(response["results"])
        
        if len(response["results"]) < 100:
            break
        offset += 100
    
    return orders

Get a Specific Order

def get_order(client, shop_id, receipt_id):
    """Get details for a specific order."""
    return client.get(f"/application/shops/{shop_id}/receipts/{receipt_id}")

The Receipt Object

A receipt contains comprehensive order information:

{
    "receipt_id": 1234567890,
    "receipt_type": 0,
    "status": "paid",
    "buyer_user_id": 98765432,
    "buyer_email": "customer@email.com",
    "name": "Customer Name",
    "total_price": {"amount": 1498, "divisor": 100, "currency_code": "USD"},
    "subtotal": {"amount": 1400, "divisor": 100, "currency_code": "USD"},
    "total_tax_cost": {"amount": 98, "divisor": 100, "currency_code": "USD"},
    "create_timestamp": 1699900000,
    "is_paid": true,
    "transactions": [...]
}

Working with Transactions

Each transaction represents one item purchased:

def get_order_items(client, shop_id, receipt_id):
    """Get individual items from an order."""
    order = get_order(client, shop_id, receipt_id)
    
    items = []
    for transaction in order.get("transactions", []):
        items.append({
            "transaction_id": transaction["transaction_id"],
            "listing_id": transaction["listing_id"],
            "title": transaction["title"],
            "quantity": transaction["quantity"],
            "price": transaction["price"]["amount"] / transaction["price"]["divisor"]
        })
    
    return items

Digital Product Specifics

Auto-Fulfillment

Digital products are automatically marked as fulfilled when payment completes. You don’t need to “ship” them:

def is_digital_order(order):
    """Check if an order contains only digital items."""
    for transaction in order.get("transactions", []):
        if not transaction.get("is_digital", False):
            return False
    return True

Download Status

Unfortunately, the API doesn’t expose whether customers have downloaded their files. This is a limitation you’ll need to work around (e.g., ask in messages if they need help).

Order Filtering

Filter by Product Type

def get_orders_by_listing(client, shop_id, listing_id):
    """Get all orders for a specific listing."""
    all_orders = get_recent_orders(client, shop_id, limit=100)
    
    matching = []
    for order in all_orders:
        for transaction in order.get("transactions", []):
            if transaction["listing_id"] == listing_id:
                matching.append(order)
                break
    
    return matching

Filter by Status

def get_orders_by_status(client, shop_id, status):
    """Get orders with a specific status."""
    response = client.get(
        f"/application/shops/{shop_id}/receipts",
        params={"was_paid": status == "paid"}
    )
    return response["results"]

Practical Order Reports

Daily Sales Summary

See code/orders.py for complete implementation.

def daily_sales_summary(client, shop_id, date=None):
    """Generate a sales summary for a specific day."""
    if date is None:
        date = datetime.now()
    
    start = datetime(date.year, date.month, date.day)
    end = start + timedelta(days=1)
    
    orders = get_orders_in_range(client, shop_id, start, end)
    
    total_revenue = sum(
        o["total_price"]["amount"] / o["total_price"]["divisor"] 
        for o in orders
    )
    
    return {
        "date": date.strftime("%Y-%m-%d"),
        "order_count": len(orders),
        "total_revenue": total_revenue,
        "average_order": total_revenue / len(orders) if orders else 0
    }

Product Performance Report

def product_sales_report(client, shop_id, days=30):
    """See which products sold in the last N days."""
    end_date = datetime.now()
    start_date = end_date - timedelta(days=days)
    
    orders = get_orders_in_range(client, shop_id, start_date, end_date)
    
    product_sales = {}
    for order in orders:
        for transaction in order.get("transactions", []):
            listing_id = transaction["listing_id"]
            if listing_id not in product_sales:
                product_sales[listing_id] = {
                    "title": transaction["title"],
                    "quantity": 0,
                    "revenue": 0
                }
            product_sales[listing_id]["quantity"] += transaction["quantity"]
            product_sales[listing_id]["revenue"] += (
                transaction["price"]["amount"] / transaction["price"]["divisor"]
            )
    
    # Sort by revenue
    return sorted(
        product_sales.values(), 
        key=lambda x: x["revenue"], 
        reverse=True
    )

Handling Customer Issues

Finding Customer Orders

When a customer contacts you, find their orders:

def find_customer_orders(client, shop_id, buyer_email=None, buyer_user_id=None):
    """Find orders from a specific customer."""
    all_orders = get_recent_orders(client, shop_id, limit=100)
    
    matching = []
    for order in all_orders:
        if buyer_email and order.get("buyer_email") == buyer_email:
            matching.append(order)
        elif buyer_user_id and order.get("buyer_user_id") == buyer_user_id:
            matching.append(order)
    
    return matching

Common Customer Issues

For digital products, common issues include:

  1. Can’t find download link: Direct them to Purchases > Download Files
  2. Wrong file format: Check if you offer the format they need
  3. Corrupted file: Verify your uploaded files are intact
  4. Didn’t receive email: Download is on Etsy, not email

Export to CSV

Export orders for accounting or analysis:

import csv
from pathlib import Path

def export_orders_csv(orders, filename="orders.csv"):
    """Export orders to CSV file."""
    fieldnames = ["receipt_id", "date", "customer", "total", "items"]
    
    with open(filename, "w", newline="") as f:
        writer = csv.DictWriter(f, fieldnames=fieldnames)
        writer.writeheader()
        
        for order in orders:
            writer.writerow({
                "receipt_id": order["receipt_id"],
                "date": datetime.fromtimestamp(order["create_timestamp"]).isoformat(),
                "customer": order.get("name", "N/A"),
                "total": order["total_price"]["amount"] / order["total_price"]["divisor"],
                "items": len(order.get("transactions", []))
            })
    
    return Path(filename).absolute()

Order Notifications

While the API doesn’t have push notifications, you can poll for new orders:

import time

def monitor_new_orders(client, shop_id, check_interval=300):
    """Poll for new orders every N seconds."""
    last_check = datetime.now()
    
    while True:
        orders = get_orders_in_range(
            client, shop_id, 
            last_check, 
            datetime.now()
        )
        
        for order in orders:
            print(f"New order: {order['receipt_id']} - ${order['total_price']['amount']/100}")
            # Could trigger email, Slack notification, etc.
        
        last_check = datetime.now()
        time.sleep(check_interval)

For production, consider using Etsy’s webhooks (limited) or a proper job scheduler.

What’s Next

Digital products often come in variations (PDF vs PNG, A4 vs Letter). The next chapter covers managing product variations and inventory.


← Previous: Digital Files Next: Inventory & Variations →

← Back to Table of Contents



>> You can subscribe to my mailing list here for a monthly update. <<