Skip to main content

Tracking Page Business Rules

This document outlines the business logic and rules for the order tracking system implemented in the theme repository.

Overviewโ€‹

The order tracking system displays order status, shipment tracking, and delivery information to customers. It integrates data from:

  • Shopify Admin API (order data, fulfillments, refunds)
  • Aftership API (carrier tracking, delivery estimates)
  • Platform API (/v1/tracking/get-order-with-shipments)

Order Status Determinationโ€‹

Status Priority Hierarchyโ€‹

The system uses a waterfall logic where higher-priority conditions override lower ones:

1. ORDER-LEVEL STATES (Highest Priority)
- Order canceled โ†’ "Canceled"
- Order refunded โ†’ "Refunded"
- Exception checkpoint exists โ†’ "Exception"

2. SHIPMENT-LEVEL REFUND STATES
- All line items returned โ†’ "Returned"
- All line items canceled โ†’ "Canceled"
- Some line items refunded:
* All refunded โ†’ "Refunded"
* Partial refund โ†’ "Partially Refunded"

3. AFTERSHIP TRACKING STATES
- Has Aftership status (not Delivered) โ†’ Use Aftership status
- Delivered with date โ†’ "Delivered on {date}"
- Fulfilled but no Aftership โ†’ "Delivered" (expired tracking)

4. PRODUCTION TIMELINE (MTO Only)
- Before production start โ†’ "Ordered"
- After production start, no transit scan โ†’ "In Production"
- Has transit scan โ†’ "In Transit"

Key Ruleโ€‹

Financial states (refunds/cancellations) always override tracking states, even if Aftership shows "Delivered".

Product Type Detectionโ€‹

Made-to-Order (MTO)โ€‹

const isMTO = lineItems.some(item =>
item.properties.find(prop => prop.name === "_MADE-TO-ORDER")
)

Preorderโ€‹

const isPREORDER = lineItems.some(item =>
item.properties.find(prop =>
prop.name === "PREORDER" || prop.name === "_PREORDER"
)
)

Ready-to-Ship (RTS)โ€‹

const isRTS = !isMTO && !isPREORDER

Production Timeline Logic (MTO Only)โ€‹

See Production Timeline for detailed production timeline documentation including:

  • Timeline selection based on _PRODUCTION_DAYS line item property
  • Predefined timeline configurations (49 and 56 days)
  • Custom timeline calculation formula
  • Timeline phases and messaging

Expected Delivery Date Calculationโ€‹

Priority Cascade (First Match Wins)โ€‹

1. PREORDER DATE
- Extract from line item property value
- Format: MM-DD-YYYY in "_PREORDER" or "PREORDER" property
- Uses max date if multiple preorder items
- Display: "until" format

2. MTO SHIPPING LINE CODE
- Extract weeks from shipping_line.code (e.g., "4 weeks")
- Calculate: order.processed_at + (weeks ร— 7 days)
- Display: "until" format

3. FIRST ESTIMATED DELIVERY
- shipment.first_estimated_delivery (manual override)
- Can be "specific" or "range"

4. CUSTOM ESTIMATED DELIVERY
- shipment.custom_estimated_delivery (manual override)
- Can be "specific" or "range"

5. AFTERSHIP ESTIMATED DELIVERY DATE
- shipment.aftership_estimated_delivery_date
- From carrier API
- Can be "specific" or "range"

6. LATEST ESTIMATED DELIVERY
- shipment.latest_estimated_delivery (updated estimate)
- Can be "specific" or "range"

7. EXPECTED DELIVERY
- shipment.expected_delivery (fallback)
- Always "specific" format

Display Formatsโ€‹

  • Specific: "Expected on Tuesday, April 17"
  • Range: "Expected between Monday, April 15 - Friday, April 20"
  • Until: "Expected on Friday, April 20"
  • Delivered: "Delivered Tuesday, April 17" (overrides all formats)

When NOT to Show Expected Dateโ€‹

Don't display expected delivery date if:

  • Status is: Delivered, Out for Delivery, Available for Pickup, Attempt Fail
  • Order state: Expired, Canceled, Refunded
  • Status is Exception
  • RTS order without real carrier scans (prevents false promises)

Real Scan Detectionโ€‹

Purposeโ€‹

Determines if a shipment has actual carrier scans vs. just label creation.

Real Scan Tagsโ€‹

const REAL_SCAN_TAGS = [
'InTransit',
'OutForDelivery',
'Delivered',
'AvailableForPickup',
'AttemptFail',
'Exception'
];

const hasRealScan = shipment.checkpoints.some(cp =>
REAL_SCAN_TAGS.includes(cp?.tag)
);

Business Rule: RTS Without Real Scansโ€‹

For Ready-to-Ship orders without real carrier scans:

  • Don't show expected delivery date
  • Don't show detailed tracking information
  • Status remains "Ordered" until carrier physically scans package

Why: Prevents misleading customers with delivery dates when carrier has only received shipping label info but hasn't actually received the package.

Refund Display Logicโ€‹

Line Item Levelโ€‹

// Show strikethrough price + "(Refunded)" if:
if (
totalRefundAmount > 0 &&
refund_line_items.some(refundItem =>
refundItem.line_item_id === lineItem.id &&
refundItem.subtotal > 0
)
) {
display: "<strike>$XX.XX</strike> (Refunded)"
}

Order Summary Levelโ€‹

// Calculate total refunded amount
totalRefundAmount = order.refunds
.flatMap(refund => refund.transactions)
.filter(tx => tx.status === "success")
.reduce((sum, tx) => sum + Number(tx.amount), 0);

// Adjust order total
displayTotal = order.total_price - totalRefundAmount;

Status by Refund Typeโ€‹

// Determine which line items in THIS shipment are refunded
refundedInShipment = allRefundedLineItems.filter(refundItem =>
shipment.line_items.some(shipItem =>
shipItem.id === refundItem.line_item_id
)
);

// All items refunded
if (refundedInShipment.length === shipment.line_items.length) {
if (all have restock_type === "return") {
status = "Returned"
} else {
status = "Refunded"
}
}
// Some items refunded
else if (refundedInShipment.length > 0) {
status = "Partially Refunded"
}

Business Rule: Partial refunds can coexist with other statuses. A shipment can show "Partially Refunded" while also displaying "Delivered" if tracking confirms delivery.

Order Cancellationโ€‹

Self-Service Cancellation Windowโ€‹

const hoursPassedSinceOrdered = Math.abs(
differenceInHours(now, order.processedAt)
);

const canCancel =
hoursPassedSinceOrdered < 1 &&
!['fulfilled', 'shipped', 'in_transit'].includes(fulfillmentStatus) &&
!['paid', 'refunded', 'partially_refunded'].includes(financialStatus);

Business Rulesโ€‹

  • 1-hour window for customer self-service cancellation
  • After 1 hour, customer must contact support
  • Cannot cancel if already fulfilled/shipped
  • Cannot cancel if already paid/refunded

Business Context: Balances customer flexibility with fulfillment efficiency. Prevents cancellations after warehouse has already started processing.

Return Eligibilityโ€‹

Display Ruleโ€‹

// Show "Return Items" button if:
const canReturn = orderStatus.canReturn; // from Aftership data

if (canReturn) {
display button linking to "https://birdygrey.happyreturns.com/"
}

Determined Byโ€‹

  • Order is delivered
  • Within return window (typically 30 days from delivery)
  • Not final sale items (checked via line item _is_final_sale property)

Final Sale Itemsโ€‹

Detectionโ€‹

const isFinalSale = lineItem.properties.some(prop =>
(prop.name === "_is_final_sale" || prop.name === "is_final_sale") &&
prop.value === "true"
);

Displayโ€‹

If final sale:

  • Show red "Final Sale" badge on line item
  • Affects return eligibility (cannot return)

Key Business Decisions Summaryโ€‹

  1. Financial states override tracking states - Refunds/cancellations are more important than delivery status

  2. MTO products get production timeline UI - Sets proper expectations for made-to-order items with transparent progress updates

  3. RTS without scans stays "Ordered" - Prevents false delivery promises when carrier hasn't physically received package

  4. Multiple manual override points - Allows CS team to manually set delivery dates via first/custom/latest estimated delivery fields

  5. Privacy by default for guests - Only show full PII to verified customers; guests see limited info

  6. Progressive production messaging - Keeps customers engaged during long MTO timelines with meaningful updates

  7. 1-hour cancellation window - Balances customer flexibility with fulfillment efficiency

  8. Filter confusing tracking events - Only show meaningful checkpoints to customers (no China scans, customs events, etc.)

  9. Handle partial refunds gracefully - Show accurate item-level refund state while maintaining overall order context

  10. Expired tracking = Delivered - Prevents "stuck" orders in tracking UI for old or manually fulfilled orders

Hydrogen Storefrontโ€‹

  • app/routes/account.orders.tsx - Fetches order data with Aftership integration
  • app/components/Order/order.component.tsx - Order listing with tracking link
  • app/lib/account/utils.ts - Status calculation helpers

Theme Repositoryโ€‹

See UI & Display - Related Files for theme-specific files.

API Integrationโ€‹

Platform API Endpointโ€‹

GET {platformUrl}/v1/tracking/get-order-with-shipments?{input}&email={email}

Environment Detection:

  • Production: platform.birdygrey.com (when store domain includes "birdygrey" or is "birdygrace.myshopify.com")
  • Staging: platform.birdystaging.com (all other stores)

Input Parameter:

  • id={orderId} OR number={orderNumber}
  • Requires email={customerEmail} for guest access

Response: Complete order object including:

  • Shopify order data (line items, fulfillments, refunds)
  • Aftership shipment data (tracking, checkpoints, estimates)
  • Enriched fields (rush fees, production flags)