Skip to main content
Openfront provides a sophisticated claims and returns system that handles the complete lifecycle of product returns, replacements, and refunds. The system tracks items, manages inventory, processes refunds, and maintains detailed audit trails.

Claims Architecture

The claims system consists of three main components:
  1. Claim Orders - Container for the claim with type and status
  2. Claim Items - Individual items being claimed
  3. Returns - Physical return of products

Claim Orders

Claim orders represent customer requests for returns or replacements:
ClaimOrder {
  type: "replace" | "refund"     // What customer wants
  paymentStatus: "na" | "not_refunded" | "refunded"
  fulfillmentStatus: "not_fulfilled" | "partially_fulfilled" | "fulfilled"
  metadata: json
  
  // Relationships
  order: → Order                // Original order
  returnOrder: → Return          // Associated return
  claimItems: [→ ClaimItem]      // Items being claimed
  additionalItems: [→ OrderLineItem]  // Replacement items
  fulfillments: [→ Fulfillment]  // Replacement shipments
}

Claim Types

Replace - Customer receives replacement product:
  • Original item returned
  • New item shipped
  • No refund processed
  • Useful for defective or wrong items
Refund - Customer receives money back:
  • Original item returned
  • Refund processed to payment method
  • No replacement shipped
  • Useful for unwanted items

Creating Claims

Step 1: Identify Order and Items

Customer initiates claim for specific items:
query GetOrderForClaim {
  order(where: { id: "order_id" }) {
    id
    displayId
    status
    lineItems {
      id
      title
      quantity
      variant {
        id
        sku
        title
      }
    }
  }
}

Step 2: Create Claim Order

Initiate the claim:
mutation CreateClaim {
  createClaimOrder(data: {
    type: "refund"
    order: { connect: { id: "order_id" } }
    claimItems: {
      create: [
        {
          reason: "wrong_item"
          note: "Received blue shirt instead of black"
          quantity: 1
          item: { connect: { id: "line_item_id" } }
          variant: { connect: { id: "variant_id" } }
        }
      ]
    }
  }) {
    id
    type
    claimItems {
      id
      reason
      quantity
    }
  }
}

Step 3: Create Return

Track physical return of products:
mutation CreateReturn {
  createReturn(data: {
    order: { connect: { id: "order_id" } }
    claimOrder: { connect: { id: "claim_id" } }
    status: "requested"
    returnItems: {
      create: [
        {
          quantity: 1
          item: { connect: { id: "line_item_id" } }
        }
      ]
    }
  }) {
    id
    status
    returnItems {
      id
      quantity
    }
  }
}

Claim Items

Detailed information about items being claimed:
ClaimItem {
  reason: "missing_item" | "wrong_item" | "production_failure" | "other"
  note: string               // Customer explanation
  quantity: integer          // Number of items
  
  claimOrder: → ClaimOrder
  item: → OrderLineItem       // Original line item
  variant: → ProductVariant   // Product variant
  images: [→ ClaimImage]      // Evidence photos
  tags: [→ ClaimTag]          // Classification tags
}

Claim Reasons

  • missing_item - Item not received
  • wrong_item - Incorrect product sent
  • production_failure - Defective or damaged
  • other - Other reasons (see note)

Adding Evidence

Customers can attach photos:
mutation AddClaimImage {
  createClaimImage(data: {
    url: "https://cdn.store.com/claims/damage-photo.jpg"
    claimItem: { connect: { id: "claim_item_id" } }
  }) {
    id
    url
  }
}

Tagging Claims

Categorize claims for analysis:
mutation TagClaim {
  createClaimTag(data: {
    value: "shipping_damage"
    claimItem: { connect: { id: "claim_item_id" } }
  }) {
    id
    value
  }
}

Return Processing

Manage the physical return of products:
Return {
  status: "requested" | "received" | "requires_action" | "canceled"
  refundAmount: integer      // Amount to refund
  receivedAt: timestamp      // When items received
  metadata: json
  
  order: → Order
  returnItems: [→ ReturnItem]
  refund: → Refund            // Processed refund
  claimOrder: → ClaimOrder
  reason: → ReturnReason      // Categorization
  location: → Location        // Return destination
}

Return Workflow

1
Customer Initiates Return
2
Customer requests return through claims system
3
Generate Return Label
4
Provide shipping label for return:
5
const label = await createShippingLabel({
  order: returnOrder,
  type: 'return',
  fromAddress: order.shippingAddress,
  toAddress: warehouse.address,
});
6
Mark as Shipped
7
Customer ships return:
8
mutation UpdateReturn {
  updateReturn(
    where: { id: "return_id" }
    data: {
      status: "shipped"
      trackingNumber: "1Z999AA10123456784"
    }
  ) {
    id
    status
    trackingNumber
  }
}
9
Receive Items
10
Warehouse receives and inspects:
11
mutation ReceiveReturn {
  updateReturn(
    where: { id: "return_id" }
    data: {
      status: "received"
      receivedAt: "2024-03-15T10:30:00Z"
      location: { connect: { id: "location_id" } }
    }
  ) {
    id
    status
    receivedAt
  }
}
12
Process Refund or Replacement
13
Complete the claim based on type

Return Items

Track individual items in returns:
ReturnItem {
  quantity: integer
  isRequested: boolean
  receivedQuantity: integer
  note: string
  
  return: → Return
  item: → OrderLineItem
}

Partial Returns

Handle returns where not all items received:
mutation UpdateReturnItem {
  updateReturnItem(
    where: { id: "return_item_id" }
    data: {
      receivedQuantity: 1
      note: "Only 1 of 2 items received"
    }
  ) {
    id
    quantity
    receivedQuantity
    note
  }
}

Refund Processing

Process refunds for returned items:
Refund {
  amount: integer
  reason: string
  note: string
  
  order: → Order
  payment: → Payment
  return: → Return
}

Creating Refunds

mutation ProcessRefund {
  createRefund(data: {
    amount: 9900
    reason: "Customer return"
    note: "Item returned in good condition"
    order: { connect: { id: "order_id" } }
    payment: { connect: { id: "payment_id" } }
    return: { connect: { id: "return_id" } }
  }) {
    id
    amount
    reason
  }
}

Refund to Payment Method

Process refund through payment provider:
import { refundPaymentFunction } from '@/features/integrations/payment';

async function processRefund(refund: Refund) {
  const payment = refund.payment;
  const provider = payment.paymentProvider;
  
  // Get the adapter for this provider
  const adapter = await getPaymentAdapter(provider.code);
  
  // Process refund through provider
  const result = await adapter.refundPaymentFunction({
    paymentId: payment.providerId,
    amount: refund.amount,
  });
  
  // Update claim status
  await updateClaimOrder(refund.order.claimOrder.id, {
    paymentStatus: 'refunded',
  });
  
  return result;
}

Replacement Processing

Handle product replacements:

Adding Replacement Items

mutation AddReplacementItem {
  updateClaimOrder(
    where: { id: "claim_id" }
    data: {
      additionalItems: {
        create: [
          {
            title: "Black T-Shirt - Medium"
            quantity: 1
            variant: { connect: { id: "correct_variant_id" } }
            order: { connect: { id: "order_id" } }
          }
        ]
      }
    }
  ) {
    id
    additionalItems {
      title
      quantity
    }
  }
}

Fulfilling Replacements

Ship replacement items:
mutation CreateReplacementFulfillment {
  createFulfillment(data: {
    claimOrder: { connect: { id: "claim_id" } }
    items: {
      create: [
        {
          quantity: 1
          item: { connect: { id: "additional_item_id" } }
        }
      ]
    }
  }) {
    id
    trackingNumber
    items {
      quantity
    }
  }
}

Return Reasons

Categorize returns for analysis:
ReturnReason {
  value: string
  label: string
  description: string
  
  returns: [→ Return]
}

Common Return Reasons

mutation CreateReturnReasons {
  createReturnReason(data: { 
    value: "defective"
    label: "Defective Product"
    description: "Product is damaged or not working"
  }) { id }
  
  createReturnReason(data: {
    value: "wrong_item"
    label: "Wrong Item"
    description: "Received different product than ordered"
  }) { id }
  
  createReturnReason(data: {
    value: "not_as_described"
    label: "Not As Described"
    description: "Product differs from description"
  }) { id }
  
  createReturnReason(data: {
    value: "changed_mind"
    label: "Changed Mind"
    description: "Customer no longer wants product"
  }) { id }
}

Claims Dashboard

View and manage all claims:
query ClaimsList {
  claimOrders(
    orderBy: [{ createdAt: desc }]
    take: 20
  ) {
    id
    type
    paymentStatus
    fulfillmentStatus
    createdAt
    order {
      displayId
      email
    }
    claimItems {
      id
      reason
      quantity
      item {
        title
      }
    }
  }
}

Filtering Claims

query FilteredClaims($type: String, $status: String) {
  claimOrders(
    where: {
      AND: [
        { type: { equals: $type } }
        { paymentStatus: { equals: $status } }
      ]
    }
  ) {
    id
    type
    paymentStatus
    order {
      displayId
    }
  }
}

Inventory Updates

Claims automatically update inventory:

Return Received

When items are received, inventory increases:
// Automatically creates stock movement
{
  type: "return",
  quantity: 1,
  reason: "Customer return",
  note: "Return ID: ret_123, Claim ID: claim_456"
}

Replacement Shipped

When replacement ships, inventory decreases:
// Automatically creates stock movement
{
  type: "adjustment",
  quantity: -1,
  reason: "Claim replacement",
  note: "Claim ID: claim_456"
}

Best Practices

Claims Policy

  • Clearly communicate return windows
  • Define acceptable return conditions
  • Specify who pays return shipping
  • Set expectations for refund timing

Processing Returns

  • Inspect returned items promptly
  • Document item condition
  • Update inventory immediately
  • Process refunds within stated timeframe

Customer Communication

  • Acknowledge claim receipt
  • Provide return shipping labels
  • Send tracking for replacements
  • Notify when refunds are processed

Data Analysis

  • Track return reasons
  • Identify problem products
  • Monitor return rates by category
  • Use insights to improve products

Analytics and Reporting

Return Rate by Product

query ProductReturnRates {
  products {
    id
    title
    productVariants {
      sku
      claimItems {
        id
        reason
      }
    }
  }
}

Common Return Reasons

function analyzeReturnReasons(claims: ClaimOrder[]) {
  const reasons = claims.flatMap(c => 
    c.claimItems.map(i => i.reason)
  );
  
  const counts = reasons.reduce((acc, reason) => {
    acc[reason] = (acc[reason] || 0) + 1;
    return acc;
  }, {} as Record<string, number>);
  
  return Object.entries(counts)
    .sort(([,a], [,b]) => b - a)
    .map(([reason, count]) => ({ reason, count }));
}

Build docs developers (and LLMs) love