Receber por email
← Voltar para o catálogo

spree-shipping-fulfillment

Build and customize Spree's shipping and fulfillment — ShippingMethod, ShippingCategory, Zone/ZoneMember, ShippingRate, the Stock::Estimator service, StockLocation/StockItem/StockMovement, multi-shipment orders, ShippingCalculator classes (FlatRate, FlatPercentItemTotal, PerItem, FlexiRate), shipment state machine, returns (ReturnAuthorization → CustomerReturn → Reimbursement → Refund), and integr

34estrelas
Atualizado há 5 meses

Ver no GitHub ↗ Licença: MIT

Como adicionar

/plugin marketplace add OrcaQubits/agentic-commerce-skills-plugins

O comando exato pode variar conforme o repositório. Confira o README no GitHub.

Para o autor da skill

Cole no README do seu repo

Mostra que sua skill está catalogada na Skillteca, gera backlink e tráfego rastreável.

[![Skillteca](https://www.skillteca.com.br/api/badge/spree-shipping-fulfillment/svg)](https://www.skillteca.com.br/skills/spree-shipping-fulfillment)

Alerta por categoria

Receba novas skills de Design e Frontend toda segunda

1 email curto, só com as skills novas de Design e Frontend. 4 minutos de leitura, sem spam, cancela com 1 clique.

Você confirma o email no primeiro envio. Sem spam. Cancela com 1 clique.

Ler descrição completa

Spree Shipping & Fulfillment

Before writing code

Fetch live docs:

  1. Fetch https://spreecommerce.org/docs/developer/core-concepts/architecture (shipping section) for the current model graph.
  2. Inspect Spree::Stock::Estimator and Spree::Stock::Splitter in the live spree gem source.
  3. Check the live Spree::ShippingMethod source for column names and associations.
  4. For carrier integrations, search community gems (spree-active-shipping, spree-ups, spree-fedex) — verify which are maintained for v5.
  5. Check release notes for any shipping-related changes (Markets in v5.4 affect shipping availability per region).

Conceptual Architecture

The Core Models

| Model | Purpose | |-------|---------| | ShippingMethod | Named shipping option (UPS Ground, FedEx 2-Day) | | ShippingRate | Computed cost option offered to the user during checkout | | ShippingCategory | Categorize products by shipping needs (Hazmat, Frozen, Standard) | | Zone / ZoneMember | Geographic regions a method ships to | | Shipment | A subset of order items shipped together from one StockLocation | | InventoryUnit | One unit of a Variant in a Shipment | | StockLocation | Physical/logical warehouse | | StockItem | Count of a Variant in a StockLocation | | StockMovement | Append-only ledger of stock changes | | StockTransfer | Move stock between StockLocations |

Why Multiple Shipments per Order?

If an order's items span multiple StockLocations, Spree creates one Shipment per location. The Spree::Stock::Splitter allocates items by stock availability and configured policy (closest, fastest, cheapest, etc.).

ShippingMethod Availability

A ShippingMethod is offered for a Shipment if:

  • The ShippingMethod's Zones include the destination
  • The Variants' ShippingCategories overlap with the ShippingMethod's allowed categories
  • The ShippingMethod is enabled for the current Store
  • (v5.4+) The Market for the destination allows this ShippingMethod

Shipping Calculators

Polymorphic Calculator (same base class as promotion/tax calculators):

| Calculator | Math | |------------|------| | Spree::Calculator::Shipping::FlatRate | Fixed cost | | Spree::Calculator::Shipping::FlatPercentItemTotal | % of cart | | Spree::Calculator::Shipping::PerItem | Per-item flat | | Spree::Calculator::Shipping::FlexiRate | Tiered by total | | Spree::Calculator::Shipping::PriceSack | Free over threshold | | Spree::Calculator::Shipping::DigitalDelivery | Free for digital |

Carrier-API calculators (UPS, FedEx, USPS) come from community gems — they compute rates by calling the carrier's API live.

Stock::Estimator

Spree::Stock::Estimator.new(order).shipping_rates(package) returns the ShippingRate options for a given Package. Replaceable via Spree::Dependencies:

Spree::Dependencies.shipping_rate_estimator = MyCustomEstimator

This is the canonical extension point for custom shipping logic — preferable to decorating the model.

Shipment State Machine

pending → ready → shipped
              ↓
            canceled
  • pending — awaiting payment / stock
  • ready — paid and stocked, ready to fulfill
  • shipped — shipped_at set, tracking number captured
  • canceled — canceled with order

Returns Flow

ReturnAuthorization (authorized | canceled)
  └── ReturnItem[]
       ↓
CustomerReturn (when physically received)
  └── ReturnItem[]
       ↓
Reimbursement (pending | reimbursed | errored)
  └── Refund (against original Payment)
   OR StoreCredit

Implementation Guidance

Setting Up Shipping for a New Store

# A US zone
us = Spree::Zone.create!(name: 'US')
us.zone_members.create!(zoneable: Spree::Country.find_by(iso: 'US'))

# A shipping category
standard = Spree::ShippingCategory.find_or_create_by!(name: 'Standard')

# A shipping method
method = Spree::ShippingMethod.create!(
  name: 'Standard Shipping',
  display_on: 'both',
  shipping_categories: [standard],
  zones: [us],
  tax_category: Spree::TaxCategory.find_by(name: 'Shipping')
)
method.calculator = Spree::Calculator::Shipping::FlatRate.new(preferred_amount: 5.00)
method.save!

# A stock location
loc = Spree::StockLocation.create!(
  name: 'Main Warehouse',
  default: true,
  active: true,
  country: Spree::Country.find_by(iso: 'US')
)

Customizing Shipping Rate Selection

If you need custom logic (e.g., "always offer free shipping for Gold customers"):

Option A — Subscribe to events (preferred when reacting):

class FreeGoldShippingSubscriber < Spree::Subscriber
  subscribes_to 'order.recalculate'
  on 'order.recalculate', :apply_free_shipping
end

Option B — Swap the Estimator (preferred when modifying rate generation):

class MyEstimator < Spree::Stock::Estimator
  def shipping_rates(package, frontend_only = true)
    rates = super
    rates.each { |r| r.cost = 0 } if package.order.user&.gold_tier?
    rates
  end
end

# config/initializers/spree.rb
Spree::Dependencies.shipping_rate_estimator = MyEstimator

Adding a Carrier API Integration

Community gems for UPS/FedEx/USPS exist; verify v5 compatibility before adoption. The pattern:

  1. Add a Spree::Calculator::Shipping::CarrierName calculator
  2. Calculator's compute(package) calls the carrier's rate API
  3. Authenticate with carrier credentials stored in Rails credentials
  4. Cache rates for the cart's lifetime to avoid repeat API calls

Tracking Numbers and Webhooks

When the fulfillment provider ships:

shipment.update!(tracking: 'TRACKING123', shipped_at: Time.current)
shipment.ship!
# Fires shipment.shipped event → webhook → customer email

Stock Management

# Increase stock
stock_item.adjust_count_on_hand(10)

# Decrease (validates sufficient stock)
stock_item.reduce_count_on_hand(2)

# Transfer between locations
Spree::StockTransfer.create!(
  source_location: loc_a,
  destination_location: loc_b,
  stock_movements_attributes: [
    { quantity: 5, stock_item_id: source_item.id }
  ]
)

Backorders

If track_inventory is on and stock hits zero, Spree marks the InventoryUnit as backordered. The shipment goes into backorder state until stock returns.

To disable backorders globally:

Spree::Config[:allow_backorders] = false

Verify the exact preference name in current config.

Returns Workflow

# Customer requests return
ra = Spree::ReturnAuthorization.create!(
  order: order,
  return_items_attributes: items.map { |i| { inventory_unit_id: i.id } },
  stock_location: order.shipments.first.stock_location
)
ra.authorize!

# Warehouse receives
cr = Spree::CustomerReturn.create!(
  return_items: ra.return_items,
  stock_location: ra.stock_location
)

# Reimburse
reimb = Spree::Reimbursement.create!(
  customer_return: cr,
  order: order,
  return_items: cr.return_items
)
reimb.perform!  # creates Refunds and/or StoreCredits

Common Pitfalls

  • Missing ShippingCategory on a Product → no ShippingMethod can match → checkout fails at delivery.
  • Zone doesn't include the country → no ShippingMethod offered.
  • Multiple StockLocations with overlapping inventory → unexpected multi-shipment splits. Configure propagate_all_variants or pin to a primary location.
  • Carrier API rate-limited during checkout → cache rates, fall back to flat rate on error.
  • Forgetting to fire shipped! when manually marking a shipment — no event, no email, no webhook.
  • Stock movements without going through adjust_count_on_hand — bypasses the ledger and breaks reconciliation.

Always verify model relationships and calculator names against the live source — shipping is one of the most decoupled subsystems and has community gems that vary in v5 readiness.

Compartilhar X LinkedIn