Chapter 7: Orders and Transactions

Understanding Etsy’s Order Structure

When a customer makes a purchase on Etsy, the system creates several related objects:

For digital products, orders are processed immediately since there’s no shipping delay.

Order Data Model

Receipt (Order)
├── receipt_id: Unique order identifier
├── buyer_user_id: Customer's user ID
├── buyer_email: Customer's email (with permission)
├── create_timestamp: When order was placed
├── payment_method: How customer paid
├── transactions: List of items purchased
│   ├── Transaction 1
│   │   ├── listing_id: Product purchased
│   │   ├── quantity: Number purchased
│   │   ├── price: Price per item
│   │   └── shipping_cost: (0 for digital)
│   └── Transaction 2...
├── subtotal: Order subtotal
├── total_price: Final price
├── discount_amt: Any discounts applied
└── status: Order status

Orders and Transactions Module

# etsy_client/orders.py
"""Order and transaction operations for Etsy API."""

from typing import Dict, Any, Optional, List
from datetime import datetime, timedelta
from enum import Enum
from dataclasses import dataclass
from .client import EtsyClient


class ReceiptStatus(Enum):
    """Possible receipt statuses."""
    PAID = "paid"
    COMPLETED = "completed"
    OPEN = "open"
    PAYMENT_PROCESSING = "payment_processing"
    CANCELLED = "cancelled"


@dataclass
class OrderSummary:
    """Summary of an order."""
    receipt_id: int
    buyer_name: str
    total: float
    currency: str
    items: List[Dict]
    created_at: datetime
    status: str
    is_digital: bool
    
    @property
    def item_count(self) -> int:
        return sum(item.get('quantity', 1) for item in self.items)


class OrderOperations:
    """Operations for managing Etsy orders."""
    
    def __init__(self, client: EtsyClient):
        self.client = client
    
    # ==================== RECEIPTS (ORDERS) ====================
    
    def get_receipt(
        self,
        shop_id: int,
        receipt_id: int
    ) -> Dict[str, Any]:
        """
        Get a specific receipt/order.
        
        Args:
            shop_id: The shop ID
            receipt_id: The receipt ID
            
        Returns:
            Receipt data with transactions
        """
        return self.client.get(
            f"/application/shops/{shop_id}/receipts/{receipt_id}"
        )
    
    def get_shop_receipts(
        self,
        shop_id: int,
        min_created: Optional[datetime] = None,
        max_created: Optional[datetime] = None,
        was_paid: bool = True,
        limit: int = 100,
        offset: int = 0
    ) -> Dict[str, Any]:
        """
        Get receipts for a shop.
        
        Args:
            shop_id: The shop ID
            min_created: Filter by minimum creation date
            max_created: Filter by maximum creation date
            was_paid: Filter for paid orders only
            limit: Results per page (max 100)
            offset: Pagination offset
            
        Returns:
            List of receipts
        """
        params = {
            "limit": limit,
            "offset": offset,
            "was_paid": was_paid
        }
        
        if min_created:
            params["min_created"] = int(min_created.timestamp())
        if max_created:
            params["max_created"] = int(max_created.timestamp())
        
        return self.client.get(
            f"/application/shops/{shop_id}/receipts",
            params=params
        )
    
    def get_all_receipts(
        self,
        shop_id: int,
        min_created: Optional[datetime] = None,
        max_created: Optional[datetime] = None,
        was_paid: bool = True
    ) -> List[Dict[str, Any]]:
        """
        Get ALL receipts, handling pagination.
        
        Args:
            shop_id: The shop ID
            min_created: Filter by minimum creation date
            max_created: Filter by maximum creation date
            was_paid: Filter for paid orders only
            
        Returns:
            List of all receipts
        """
        all_receipts = []
        offset = 0
        limit = 100
        
        while True:
            response = self.get_shop_receipts(
                shop_id, min_created, max_created, was_paid, limit, offset
            )
            
            receipts = response.get('results', [])
            all_receipts.extend(receipts)
            
            if len(receipts) < limit:
                break
            
            offset += limit
        
        return all_receipts
    
    def get_recent_orders(
        self,
        shop_id: int,
        days: int = 30
    ) -> List[Dict[str, Any]]:
        """
        Get orders from the last N days.
        
        Args:
            shop_id: The shop ID
            days: Number of days to look back
            
        Returns:
            List of recent receipts
        """
        min_date = datetime.now() - timedelta(days=days)
        return self.get_all_receipts(shop_id, min_created=min_date)
    
    # ==================== TRANSACTIONS ====================
    
    def get_shop_transactions(
        self,
        shop_id: int,
        limit: int = 100,
        offset: int = 0
    ) -> Dict[str, Any]:
        """
        Get transactions (individual sales) for a shop.
        
        Args:
            shop_id: The shop ID
            limit: Results per page
            offset: Pagination offset
            
        Returns:
            List of transactions
        """
        return self.client.get(
            f"/application/shops/{shop_id}/transactions",
            params={"limit": limit, "offset": offset}
        )
    
    def get_transaction(
        self,
        shop_id: int,
        transaction_id: int
    ) -> Dict[str, Any]:
        """
        Get a specific transaction.
        
        Args:
            shop_id: The shop ID
            transaction_id: The transaction ID
            
        Returns:
            Transaction data
        """
        return self.client.get(
            f"/application/shops/{shop_id}/transactions/{transaction_id}"
        )
    
    def get_listing_transactions(
        self,
        shop_id: int,
        listing_id: int,
        limit: int = 100
    ) -> Dict[str, Any]:
        """
        Get all transactions for a specific listing.
        
        Args:
            shop_id: The shop ID
            listing_id: The listing ID
            limit: Maximum results
            
        Returns:
            List of transactions for the listing
        """
        return self.client.get(
            f"/application/shops/{shop_id}/listings/{listing_id}/transactions",
            params={"limit": limit}
        )
    
    # ==================== HELPER METHODS ====================
    
    def parse_receipt(self, receipt: Dict[str, Any]) -> OrderSummary:
        """
        Parse a receipt into a structured OrderSummary.
        
        Args:
            receipt: Raw receipt data from API
            
        Returns:
            OrderSummary object
        """
        transactions = receipt.get('transactions', [])
        
        items = []
        is_digital = True
        
        for txn in transactions:
            items.append({
                'listing_id': txn.get('listing_id'),
                'title': txn.get('title'),
                'quantity': txn.get('quantity', 1),
                'price': txn.get('price', {}).get('amount', 0) / 
                        txn.get('price', {}).get('divisor', 100)
            })
            
            if not txn.get('is_digital', False):
                is_digital = False
        
        total_price = receipt.get('grandtotal', {})
        total = total_price.get('amount', 0) / total_price.get('divisor', 100)
        
        return OrderSummary(
            receipt_id=receipt['receipt_id'],
            buyer_name=receipt.get('name', 'Unknown'),
            total=total,
            currency=total_price.get('currency_code', 'USD'),
            items=items,
            created_at=datetime.fromtimestamp(
                receipt.get('create_timestamp', 0)
            ),
            status=receipt.get('status', 'unknown'),
            is_digital=is_digital
        )
    
    def get_digital_orders(
        self,
        shop_id: int,
        days: int = 30
    ) -> List[OrderSummary]:
        """
        Get only digital product orders.
        
        Args:
            shop_id: The shop ID
            days: Number of days to look back
            
        Returns:
            List of digital-only OrderSummary objects
        """
        receipts = self.get_recent_orders(shop_id, days)
        
        digital_orders = []
        for receipt in receipts:
            summary = self.parse_receipt(receipt)
            if summary.is_digital:
                digital_orders.append(summary)
        
        return digital_orders


class TransactionAnalyzer:
    """Analyze transaction data for insights."""
    
    def __init__(self, transactions: List[Dict[str, Any]]):
        self.transactions = transactions
    
    def total_revenue(self) -> float:
        """Calculate total revenue from transactions."""
        total = 0
        for txn in self.transactions:
            price = txn.get('price', {})
            amount = price.get('amount', 0)
            divisor = price.get('divisor', 100)
            quantity = txn.get('quantity', 1)
            
            total += (amount / divisor) * quantity
        
        return total
    
    def sales_by_listing(self) -> Dict[int, Dict]:
        """Group sales by listing."""
        by_listing = {}
        
        for txn in self.transactions:
            listing_id = txn.get('listing_id')
            
            if listing_id not in by_listing:
                by_listing[listing_id] = {
                    'title': txn.get('title', 'Unknown'),
                    'quantity': 0,
                    'revenue': 0
                }
            
            price = txn.get('price', {})
            amount = price.get('amount', 0) / price.get('divisor', 100)
            quantity = txn.get('quantity', 1)
            
            by_listing[listing_id]['quantity'] += quantity
            by_listing[listing_id]['revenue'] += amount * quantity
        
        return by_listing
    
    def top_sellers(self, limit: int = 10) -> List[Dict]:
        """Get top selling products."""
        by_listing = self.sales_by_listing()
        
        sorted_listings = sorted(
            by_listing.items(),
            key=lambda x: x[1]['revenue'],
            reverse=True
        )
        
        return [
            {'listing_id': lid, **data}
            for lid, data in sorted_listings[:limit]
        ]
    
    def daily_revenue(self) -> Dict[str, float]:
        """Calculate revenue by day."""
        by_day = {}
        
        for txn in self.transactions:
            timestamp = txn.get('created_timestamp', 0)
            date_str = datetime.fromtimestamp(timestamp).strftime('%Y-%m-%d')
            
            price = txn.get('price', {})
            amount = price.get('amount', 0) / price.get('divisor', 100)
            quantity = txn.get('quantity', 1)
            
            if date_str not in by_day:
                by_day[date_str] = 0
            
            by_day[date_str] += amount * quantity
        
        return dict(sorted(by_day.items()))

Order Processing Scripts

Daily Orders Report

# scripts/daily_orders.py
"""Generate daily orders report."""

import sys
from pathlib import Path
from datetime import datetime, timedelta

sys.path.insert(0, str(Path(__file__).parent.parent))

from config import Config
from etsy_client import EtsyClient
from etsy_client.orders import OrderOperations, TransactionAnalyzer
from etsy_client.users import get_my_shop_id


def generate_daily_report(days: int = 1):
    """
    Generate a report of orders from the last N days.
    
    Args:
        days: Number of days to include
    """
    client = EtsyClient(
        api_key=Config.ETSY_API_KEY,
        access_token=Config.ETSY_ACCESS_TOKEN
    )
    
    shop_id = get_my_shop_id(client)
    order_ops = OrderOperations(client)
    
    # Get recent orders
    print(f"Fetching orders from the last {days} day(s)...")
    
    min_date = datetime.now() - timedelta(days=days)
    receipts = order_ops.get_all_receipts(shop_id, min_created=min_date)
    
    print(f"\n{'='*60}")
    print(f"DAILY ORDERS REPORT")
    print(f"Period: {min_date.strftime('%Y-%m-%d')} to {datetime.now().strftime('%Y-%m-%d')}")
    print(f"{'='*60}\n")
    
    if not receipts:
        print("No orders in this period.")
        return
    
    # Process orders
    total_revenue = 0
    digital_count = 0
    
    print(f"{'Order ID':<12} {'Date':<12} {'Customer':<20} {'Total':>10} {'Type':<10}")
    print("-" * 70)
    
    for receipt in receipts:
        summary = order_ops.parse_receipt(receipt)
        total_revenue += summary.total
        
        if summary.is_digital:
            digital_count += 1
        
        order_type = "Digital" if summary.is_digital else "Physical"
        
        print(
            f"{summary.receipt_id:<12} "
            f"{summary.created_at.strftime('%Y-%m-%d'):<12} "
            f"{summary.buyer_name[:18]:<20} "
            f"${summary.total:>9.2f} "
            f"{order_type:<10}"
        )
    
    print("-" * 70)
    print(f"\nSUMMARY:")
    print(f"  Total Orders: {len(receipts)}")
    print(f"  Digital Orders: {digital_count}")
    print(f"  Physical Orders: {len(receipts) - digital_count}")
    print(f"  Total Revenue: ${total_revenue:.2f}")
    print(f"  Average Order Value: ${total_revenue / len(receipts):.2f}")


def export_orders_csv(days: int, output_file: str):
    """
    Export orders to CSV file.
    
    Args:
        days: Number of days to include
        output_file: Output CSV file path
    """
    import csv
    
    client = EtsyClient(
        api_key=Config.ETSY_API_KEY,
        access_token=Config.ETSY_ACCESS_TOKEN
    )
    
    shop_id = get_my_shop_id(client)
    order_ops = OrderOperations(client)
    
    min_date = datetime.now() - timedelta(days=days)
    receipts = order_ops.get_all_receipts(shop_id, min_created=min_date)
    
    with open(output_file, 'w', newline='', encoding='utf-8') as f:
        writer = csv.writer(f)
        
        # Header
        writer.writerow([
            'Receipt ID', 'Date', 'Customer', 'Email',
            'Items', 'Subtotal', 'Discount', 'Total',
            'Currency', 'Digital', 'Status'
        ])
        
        for receipt in receipts:
            summary = order_ops.parse_receipt(receipt)
            
            writer.writerow([
                summary.receipt_id,
                summary.created_at.strftime('%Y-%m-%d %H:%M:%S'),
                summary.buyer_name,
                receipt.get('buyer_email', ''),
                summary.item_count,
                receipt.get('subtotal', {}).get('amount', 0) / 100,
                receipt.get('discount_amt', {}).get('amount', 0) / 100,
                summary.total,
                summary.currency,
                'Yes' if summary.is_digital else 'No',
                summary.status
            ])
    
    print(f"✓ Exported {len(receipts)} orders to {output_file}")


if __name__ == "__main__":
    import argparse
    
    parser = argparse.ArgumentParser(description='Generate orders report')
    parser.add_argument('--days', type=int, default=1, help='Days to include')
    parser.add_argument('--export', type=str, help='Export to CSV file')
    
    args = parser.parse_args()
    
    if args.export:
        export_orders_csv(args.days, args.export)
    else:
        generate_daily_report(args.days)

Best Sellers Report

# scripts/best_sellers.py
"""Analyze best-selling products."""

import sys
from pathlib import Path
from datetime import datetime, timedelta

sys.path.insert(0, str(Path(__file__).parent.parent))

from config import Config
from etsy_client import EtsyClient
from etsy_client.orders import OrderOperations, TransactionAnalyzer
from etsy_client.users import get_my_shop_id


def analyze_best_sellers(days: int = 30, limit: int = 10):
    """
    Analyze best-selling products.
    
    Args:
        days: Period to analyze
        limit: Number of top products to show
    """
    client = EtsyClient(
        api_key=Config.ETSY_API_KEY,
        access_token=Config.ETSY_ACCESS_TOKEN
    )
    
    shop_id = get_my_shop_id(client)
    order_ops = OrderOperations(client)
    
    print(f"Analyzing sales from the last {days} days...\n")
    
    # Get all receipts
    min_date = datetime.now() - timedelta(days=days)
    receipts = order_ops.get_all_receipts(shop_id, min_created=min_date)
    
    # Extract all transactions
    all_transactions = []
    for receipt in receipts:
        transactions = receipt.get('transactions', [])
        all_transactions.extend(transactions)
    
    if not all_transactions:
        print("No transactions found in this period.")
        return
    
    # Analyze
    analyzer = TransactionAnalyzer(all_transactions)
    
    # Top sellers
    top = analyzer.top_sellers(limit)
    
    print(f"{'='*70}")
    print(f"TOP {limit} BEST SELLERS")
    print(f"Period: Last {days} days | Total Transactions: {len(all_transactions)}")
    print(f"{'='*70}\n")
    
    print(f"{'Rank':<6} {'Product':<35} {'Sales':>8} {'Revenue':>12}")
    print("-" * 65)
    
    for i, product in enumerate(top, 1):
        title = product['title'][:33] if product['title'] else 'Unknown'
        print(
            f"{i:<6} {title:<35} "
            f"{product['quantity']:>8} "
            f"${product['revenue']:>11.2f}"
        )
    
    print("-" * 65)
    
    # Summary stats
    total_revenue = analyzer.total_revenue()
    top_revenue = sum(p['revenue'] for p in top)
    
    print(f"\nSUMMARY:")
    print(f"  Total Revenue: ${total_revenue:.2f}")
    print(f"  Top {limit} Revenue: ${top_revenue:.2f} ({top_revenue/total_revenue*100:.1f}%)")
    print(f"  Unique Products Sold: {len(analyzer.sales_by_listing())}")


def revenue_trends(days: int = 30):
    """
    Analyze revenue trends over time.
    
    Args:
        days: Period to analyze
    """
    client = EtsyClient(
        api_key=Config.ETSY_API_KEY,
        access_token=Config.ETSY_ACCESS_TOKEN
    )
    
    shop_id = get_my_shop_id(client)
    order_ops = OrderOperations(client)
    
    min_date = datetime.now() - timedelta(days=days)
    receipts = order_ops.get_all_receipts(shop_id, min_created=min_date)
    
    all_transactions = []
    for receipt in receipts:
        all_transactions.extend(receipt.get('transactions', []))
    
    analyzer = TransactionAnalyzer(all_transactions)
    daily = analyzer.daily_revenue()
    
    print(f"\n{'='*50}")
    print("DAILY REVENUE TREND")
    print(f"{'='*50}\n")
    
    print(f"{'Date':<12} {'Revenue':>12} {'Graph'}")
    print("-" * 50)
    
    max_revenue = max(daily.values()) if daily else 1
    
    for date, revenue in daily.items():
        bar_length = int((revenue / max_revenue) * 30)
        bar = "" * bar_length
        print(f"{date:<12} ${revenue:>10.2f} {bar}")
    
    print("-" * 50)
    print(f"\nTotal: ${sum(daily.values()):.2f}")
    print(f"Average: ${sum(daily.values()) / len(daily):.2f}/day" if daily else "")


if __name__ == "__main__":
    import argparse
    
    parser = argparse.ArgumentParser(description='Analyze best sellers')
    parser.add_argument('--days', type=int, default=30, help='Days to analyze')
    parser.add_argument('--limit', type=int, default=10, help='Number of products')
    parser.add_argument('--trends', action='store_true', help='Show revenue trends')
    
    args = parser.parse_args()
    
    if args.trends:
        revenue_trends(args.days)
    else:
        analyze_best_sellers(args.days, args.limit)

Order Notifications

# etsy_client/notifications.py
"""Order notification handling."""

from typing import Dict, Any, Callable, List
from datetime import datetime
import smtplib
from email.mime.text import MIMEText
from email.mime.multipart import MIMEMultipart
from .client import EtsyClient
from .orders import OrderOperations, OrderSummary


class OrderNotifier:
    """Send notifications for new orders."""
    
    def __init__(
        self,
        client: EtsyClient,
        shop_id: int,
        smtp_config: Dict[str, str] = None
    ):
        """
        Initialize notifier.
        
        Args:
            client: EtsyClient instance
            shop_id: Shop ID to monitor
            smtp_config: Email configuration (server, port, user, password)
        """
        self.client = client
        self.shop_id = shop_id
        self.order_ops = OrderOperations(client)
        self.smtp_config = smtp_config
        
        self._callbacks: List[Callable[[OrderSummary], None]] = []
        self._last_check: datetime = datetime.now()
        self._known_orders: set = set()
    
    def register_callback(
        self,
        callback: Callable[[OrderSummary], None]
    ):
        """Register a callback for new orders."""
        self._callbacks.append(callback)
    
    def check_new_orders(self) -> List[OrderSummary]:
        """
        Check for new orders since last check.
        
        Returns:
            List of new orders
        """
        receipts = self.order_ops.get_shop_receipts(
            self.shop_id,
            min_created=self._last_check
        )
        
        new_orders = []
        
        for receipt in receipts.get('results', []):
            receipt_id = receipt['receipt_id']
            
            if receipt_id not in self._known_orders:
                self._known_orders.add(receipt_id)
                summary = self.order_ops.parse_receipt(receipt)
                new_orders.append(summary)
                
                # Trigger callbacks
                for callback in self._callbacks:
                    try:
                        callback(summary)
                    except Exception as e:
                        print(f"Callback error: {e}")
        
        self._last_check = datetime.now()
        return new_orders
    
    def send_email_notification(
        self,
        order: OrderSummary,
        recipient: str
    ):
        """
        Send email notification for an order.
        
        Args:
            order: Order summary
            recipient: Email address to notify
        """
        if not self.smtp_config:
            raise ValueError("SMTP not configured")
        
        # Create email
        msg = MIMEMultipart('alternative')
        msg['Subject'] = f"New Etsy Order #{order.receipt_id}"
        msg['From'] = self.smtp_config['user']
        msg['To'] = recipient
        
        # Text content
        text = f"""
New order received!

Order ID: {order.receipt_id}
Customer: {order.buyer_name}
Total: ${order.total:.2f} {order.currency}
Type: {'Digital' if order.is_digital else 'Physical'}

Items:
"""
        for item in order.items:
            text += f"  - {item['title']} (x{item['quantity']}) - ${item['price']:.2f}\n"
        
        # HTML content
        html = f"""
<html>
<body>
<h2>New Order #{order.receipt_id}</h2>
<p><strong>Customer:</strong> {order.buyer_name}</p>
<p><strong>Total:</strong> ${order.total:.2f} {order.currency}</p>
<p><strong>Type:</strong> {'Digital' if order.is_digital else 'Physical'}</p>

<h3>Items:</h3>
<ul>
"""
        for item in order.items:
            html += f"<li>{item['title']} (x{item['quantity']}) - ${item['price']:.2f}</li>\n"
        
        html += "</ul></body></html>"
        
        msg.attach(MIMEText(text, 'plain'))
        msg.attach(MIMEText(html, 'html'))
        
        # Send
        with smtplib.SMTP(
            self.smtp_config['server'],
            self.smtp_config['port']
        ) as server:
            server.starttls()
            server.login(
                self.smtp_config['user'],
                self.smtp_config['password']
            )
            server.send_message(msg)
    
    def format_slack_message(self, order: OrderSummary) -> Dict:
        """
        Format order as Slack message.
        
        Args:
            order: Order summary
            
        Returns:
            Slack message payload
        """
        items_text = "\n".join(
            f"{item['title']} (x{item['quantity']})"
            for item in order.items
        )
        
        return {
            "blocks": [
                {
                    "type": "header",
                    "text": {
                        "type": "plain_text",
                        "text": f"🎉 New Order #{order.receipt_id}"
                    }
                },
                {
                    "type": "section",
                    "fields": [
                        {
                            "type": "mrkdwn",
                            "text": f"*Customer:*\n{order.buyer_name}"
                        },
                        {
                            "type": "mrkdwn",
                            "text": f"*Total:*\n${order.total:.2f} {order.currency}"
                        }
                    ]
                },
                {
                    "type": "section",
                    "text": {
                        "type": "mrkdwn",
                        "text": f"*Items:*\n{items_text}"
                    }
                }
            ]
        }

Revenue Tracking

# etsy_client/revenue.py
"""Revenue tracking and reporting."""

from typing import Dict, Any, List, Optional
from datetime import datetime, timedelta
from dataclasses import dataclass
from collections import defaultdict
from .client import EtsyClient
from .orders import OrderOperations


@dataclass
class RevenuePeriod:
    """Revenue data for a period."""
    start_date: datetime
    end_date: datetime
    gross_revenue: float
    etsy_fees: float
    net_revenue: float
    order_count: int
    transaction_count: int
    
    @property
    def average_order_value(self) -> float:
        return self.gross_revenue / self.order_count if self.order_count else 0
    
    @property
    def fee_percentage(self) -> float:
        return (self.etsy_fees / self.gross_revenue * 100) if self.gross_revenue else 0


class RevenueTracker:
    """Track and analyze revenue."""
    
    # Estimated Etsy fee structure (simplified)
    LISTING_FEE = 0.20  # Per listing
    TRANSACTION_FEE_PERCENT = 0.065  # 6.5% of sale
    PAYMENT_PROCESSING_PERCENT = 0.03  # ~3% + fixed
    PAYMENT_PROCESSING_FIXED = 0.25  # Per transaction
    
    def __init__(self, client: EtsyClient, shop_id: int):
        self.client = client
        self.shop_id = shop_id
        self.order_ops = OrderOperations(client)
    
    def calculate_fees(
        self,
        sale_amount: float,
        is_digital: bool = True
    ) -> float:
        """
        Estimate Etsy fees for a sale.
        
        Args:
            sale_amount: Sale amount
            is_digital: Whether this is a digital product
            
        Returns:
            Estimated total fees
        """
        # Transaction fee
        transaction_fee = sale_amount * self.TRANSACTION_FEE_PERCENT
        
        # Payment processing
        payment_fee = (sale_amount * self.PAYMENT_PROCESSING_PERCENT) + \
                      self.PAYMENT_PROCESSING_FIXED
        
        # No listing renewal for digital products if auto-renew
        listing_fee = 0 if is_digital else self.LISTING_FEE
        
        return transaction_fee + payment_fee + listing_fee
    
    def get_period_revenue(
        self,
        start_date: datetime,
        end_date: datetime
    ) -> RevenuePeriod:
        """
        Calculate revenue for a specific period.
        
        Args:
            start_date: Period start
            end_date: Period end
            
        Returns:
            RevenuePeriod with calculated metrics
        """
        receipts = self.order_ops.get_all_receipts(
            self.shop_id,
            min_created=start_date,
            max_created=end_date
        )
        
        gross_revenue = 0
        total_fees = 0
        transaction_count = 0
        
        for receipt in receipts:
            # Get total from receipt
            total_price = receipt.get('grandtotal', {})
            amount = total_price.get('amount', 0) / total_price.get('divisor', 100)
            gross_revenue += amount
            
            # Count transactions
            transactions = receipt.get('transactions', [])
            transaction_count += len(transactions)
            
            # Estimate fees
            is_digital = all(
                t.get('is_digital', False) for t in transactions
            )
            total_fees += self.calculate_fees(amount, is_digital)
        
        return RevenuePeriod(
            start_date=start_date,
            end_date=end_date,
            gross_revenue=gross_revenue,
            etsy_fees=total_fees,
            net_revenue=gross_revenue - total_fees,
            order_count=len(receipts),
            transaction_count=transaction_count
        )
    
    def get_monthly_revenue(
        self,
        year: int,
        month: int
    ) -> RevenuePeriod:
        """Get revenue for a specific month."""
        start = datetime(year, month, 1)
        
        if month == 12:
            end = datetime(year + 1, 1, 1)
        else:
            end = datetime(year, month + 1, 1)
        
        return self.get_period_revenue(start, end)
    
    def get_yearly_summary(
        self,
        year: int
    ) -> Dict[str, RevenuePeriod]:
        """
        Get monthly revenue for an entire year.
        
        Args:
            year: Year to analyze
            
        Returns:
            Dictionary with month names as keys
        """
        months = {}
        
        for month in range(1, 13):
            month_name = datetime(year, month, 1).strftime('%B')
            
            # Skip future months
            if datetime(year, month, 1) > datetime.now():
                break
            
            months[month_name] = self.get_monthly_revenue(year, month)
        
        return months
    
    def print_revenue_report(
        self,
        period: RevenuePeriod
    ):
        """Print a formatted revenue report."""
        print(f"\n{'='*50}")
        print("REVENUE REPORT")
        print(f"Period: {period.start_date.strftime('%Y-%m-%d')} to "
              f"{period.end_date.strftime('%Y-%m-%d')}")
        print(f"{'='*50}\n")
        
        print(f"{'Gross Revenue:':<25} ${period.gross_revenue:>12.2f}")
        print(f"{'Estimated Etsy Fees:':<25} ${period.etsy_fees:>12.2f} "
              f"({period.fee_percentage:.1f}%)")
        print(f"{'Net Revenue:':<25} ${period.net_revenue:>12.2f}")
        print()
        print(f"{'Orders:':<25} {period.order_count:>12}")
        print(f"{'Items Sold:':<25} {period.transaction_count:>12}")
        print(f"{'Avg Order Value:':<25} ${period.average_order_value:>12.2f}")

Key Takeaways

  1. Receipts contain transactions: One order can have multiple items
  2. Digital orders are instant: No shipping status to track
  3. Estimate fees: Track actual fees in your accounting system
  4. Export for accounting: Regular exports simplify tax preparation
  5. Monitor trends: Daily/weekly analysis reveals patterns

Moving Forward

With order processing understood, you’re ready to build automation around inventory and pricing. The next chapter covers dynamic pricing strategies and inventory management for digital products.


← Chapter 6: Digital Downloads and File Management Table of Contents Chapter 8: Inventory and Pricing Automation →