
Order Processing Pipeline
- 70 installs
- 41 repo stars
- Updated March 13, 2026
- finsilabs/awesome-ecommerce-skills
Implement a reliable order state machine that moves orders from pending through payment, fulfillment, and delivery with webhook-driven transitions.
About
Implements an order state machine with webhook-driven transitions across payment, fulfillment, and delivery stages. A developer uses it to make order processing reliable and idempotent.
- Order state machine from pending to delivered
- Webhook-driven state transitions
Order Processing Pipeline by the numbers
- 70 all-time installs (skills.sh)
- Ranked #3,084 of 4,347 Backend & APIs skills by installs in the Skillselion catalog
- Data as of Aug 3, 2026 (Skillselion catalog sync)
npx skills add https://github.com/finsilabs/awesome-ecommerce-skills --skill order-processing-pipelineAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 70 |
|---|---|
| repo stars | ★ 41 |
| Last updated | March 13, 2026 |
| Repository | finsilabs/awesome-ecommerce-skills ↗ |
What it does
Implement a reliable order state machine that moves orders from pending through payment, fulfillment, and delivery with webhook-driven transitions.
Files
Order Processing Pipeline
Overview
Every ecommerce order follows a lifecycle: placed → payment confirmed → fulfillment started → shipped → delivered. Shopify, WooCommerce, and BigCommerce each implement this as a built-in order status system with webhook events at each transition. The goal is to configure these transitions correctly, wire your 3PL or fulfillment system to the right webhooks, and automate side effects (confirmation emails, inventory deduction, tracking notifications) at each stage.
Custom state machine code is only needed for headless storefronts or complex multi-step B2B workflows that platforms cannot handle natively.
When to Use This Skill
- When order statuses become inconsistent (e.g., fulfilled orders showing as pending)
- When implementing automated order routing to a 3PL or fulfillment provider
- When adding order status webhooks for ERP, shipping, or returns integrations
- When building a custom headless order management system
Core Instructions
Step 1: Understand your platform's order statuses and transitions
| Platform | Order Statuses | Transition Mechanism |
|---|---|---|
| Shopify | Open → Partially Fulfilled → Fulfilled → Cancelled; Financial: Pending → Authorized → Paid → Refunded | Shopify Admin + webhooks; automated by Shopify Payments and fulfillment apps |
| WooCommerce | Pending → Processing → On Hold → Completed → Cancelled → Refunded → Failed | WooCommerce status system; transitions via admin, plugins, or wc_update_order_status() |
| BigCommerce | Pending → Awaiting Payment → Awaiting Fulfillment → Awaiting Shipment → Shipped → Completed + others | BigCommerce Admin + Orders API webhooks |
| Custom / Headless | Define your own state machine | Build with explicit transition validation |
Step 2: Configure order processing automation
---
Shopify
Configure payment → order confirmation flow: 1. Shopify automatically moves orders from Open to Paid when Shopify Payments confirms the charge 2. Go to Settings → Notifications to verify the Order confirmation email is enabled — this fires automatically on payment 3. For non-Shopify payment methods, verify the gateway sends a payment success signal to Shopify; if orders stay Pending payment after payment, check your gateway settings
Set up automatic fulfillment for digital products: 1. Go to Settings → Shipping and delivery → Fulfillment 2. For digital products, enable Automatically fulfill the digital items in the order
Connect a 3PL or fulfillment center: 1. Install your fulfillment provider's Shopify app (ShipBob, ShipHero, Whiplash, Amazon FBA, etc.) from the Shopify App Store 2. Go to Settings → Shipping and delivery → Fulfillment services and add the service 3. The app subscribes to Shopify's Order created or Order paid webhooks and automatically receives new orders 4. When the 3PL ships an order, it pushes the tracking number back to Shopify via the Fulfillment API, which updates the order to Fulfilled and sends the shipping confirmation email automatically
Manual fulfillment workflow: 1. Go to Orders → Unfulfilled tab to see all orders awaiting fulfillment 2. Click an order and select Mark as fulfilled after shipping; enter the tracking number 3. The customer receives an automated tracking email when the order is marked fulfilled
Set up order webhooks for external systems (ERP, etc.): 1. Go to Settings → Notifications → Webhooks (or use the Partner Dashboard for app-level webhooks) 2. Add webhooks for: Order creation, Order payment, Order fulfillment, Order cancellation 3. Each webhook sends the full order JSON to your endpoint for processing
WooCommerce
Understand the default order flow:
- Pending payment → customer placed order, not yet paid
- Processing → payment received; order is being prepared (default for paid orders)
- Completed → order delivered; manually set or automated by shipping plugin
- On hold → payment uncertain (e.g., bank transfer awaiting confirmation)
Configure automatic status transitions: 1. Go to WooCommerce → Settings → Payments → [Gateway] → Order status for each payment method and set the status on payment (typically "Processing" for instant payment methods) 2. For virtual/downloadable orders, go to WooCommerce → Settings → Products → Downloadable products and set "Grant access to downloadable products after payment" to change orders to "Completed" automatically
Connect a shipping/fulfillment plugin: 1. Install your shipping plugin: WooCommerce ShipStation, WooCommerce ShipBob, or ELEX WooCommerce DHL/FedEx/UPS 2. Configure the plugin to pull orders with "Processing" status for fulfillment 3. When the plugin ships an order and gets a tracking number, it updates the order status to "Completed" and sends a tracking email automatically
Custom status transitions via hooks:
// In functions.php or a custom plugin — auto-complete virtual orders
add_action('woocommerce_payment_complete', 'auto_complete_virtual_orders');
function auto_complete_virtual_orders($order_id) {
$order = wc_get_order($order_id);
if ($order && $order->get_status() === 'processing') {
$all_virtual = true;
foreach ($order->get_items() as $item) {
$product = $item->get_product();
if (!$product->is_virtual()) { $all_virtual = false; break; }
}
if ($all_virtual) {
$order->update_status('completed', 'All virtual items — auto-completed.');
}
}
}Order webhooks: Install WooCommerce Webhooks (built-in) — go to WooCommerce → Settings → Advanced → Webhooks and add webhooks for order created, updated, and deleted events.
BigCommerce
Configure order status flow: 1. Go to Orders → View — BigCommerce automatically sets orders to Awaiting Fulfillment after payment is confirmed 2. Configure your payment gateway's order status mapping: go to Settings → Payment Methods → [Gateway] and set the post-payment status
Connect a fulfillment provider: 1. Go to the BigCommerce App Marketplace and install your fulfillment center's app (ShipBob, ShipStation, etc.) 2. The app connects to BigCommerce's Order webhooks and pulls new orders automatically 3. When the 3PL ships, it updates BigCommerce via the Orders API to set tracking number and change status to Shipped
Order webhooks: Go to Settings → Advanced Settings → WebHooks and add webhooks for order status changes. These are used by integrations like ERPs and accounting systems.
---
Custom / Headless
For custom storefronts, implement a state machine with explicit transition validation:
// Valid order state transitions
const VALID_TRANSITIONS = {
pending: ['confirmed', 'cancelled'],
confirmed: ['processing', 'cancelled'],
processing: ['shipped', 'cancelled'],
shipped: ['delivered', 'returned'],
delivered: ['returned', 'refunded'],
cancelled: ['refunded'],
};
async function transitionOrder(orderId, newStatus, metadata = {}) {
return db.$transaction(async (tx) => {
const order = await tx.orders.findUnique({ where: { id: orderId } });
if (!VALID_TRANSITIONS[order.status]?.includes(newStatus)) {
throw new Error(`Invalid transition: ${order.status} → ${newStatus}`);
}
const updated = await tx.orders.update({
where: { id: orderId },
data: { status: newStatus, [`${newStatus}At`]: new Date() },
});
// Append-only event log for audit trail
await tx.orderEvents.create({
data: { orderId, fromStatus: order.status, toStatus: newStatus, triggeredBy: metadata.triggeredBy ?? 'system' },
});
return updated;
});
}
// Wire to Stripe webhook — payment confirmation drives the transition
async function onPaymentSucceeded(paymentIntent) {
const orderId = paymentIntent.metadata.order_id;
const order = await db.orders.findUnique({ where: { id: orderId } });
// Idempotency — ignore if already confirmed
if (order.status !== 'pending') return;
await transitionOrder(orderId, 'confirmed', { triggeredBy: 'stripe_webhook' });
await sendOrderConfirmationEmail(orderId);
await deductInventory(orderId);
}Side effects at each transition (run outside the DB transaction to avoid blocking):
- confirmed: send confirmation email, deduct inventory, notify fulfillment provider
- shipped: send shipping email with tracking number, update tracking in customer portal
- delivered: send delivery confirmation, schedule review request (3 days later)
- cancelled: release inventory reservation, initiate refund if paid, send cancellation email
Step 3: Handle the most common edge cases
Duplicate webhook delivery: All major payment processors can deliver the same webhook multiple times. Always check the current order status before processing a transition:
if (order.status !== 'pending') return; // Already processedInventory deduction timing: Only deduct inventory after payment is confirmed (the confirmed transition), not when the order is placed. If a payment fails, you do not want inventory reserved indefinitely.
Order cancellation with partial fulfillment: If some items are shipped and others are not, platforms handle this differently. On Shopify, you can partially cancel unfulfilled line items while keeping the fulfilled items. On WooCommerce, install the WooCommerce Cancel Abandoned Order or manage this via the WooCommerce REST API.
Best Practices
- Use platform-native order management — Shopify, WooCommerce, and BigCommerce handle the core payment → fulfillment → delivery flow correctly; build on top of them rather than replacing them
- Wire fulfillment apps to the right status trigger — fulfillment providers should receive orders on payment confirmation (
order.paidon Shopify,Processingon WooCommerce), not on order creation - Log every status transition — even on platforms, add an order note (Shopify/WooCommerce both support this) or record a database event for audit purposes
- Handle idempotency on webhooks — always check the current order status before applying a transition triggered by a webhook
- Emit webhooks for external integrations — configure webhooks for all order status changes so your ERP, accounting, and CRM integrations stay in sync
Common Pitfalls
| Problem | Solution |
|---|---|
| Order confirmed twice on Shopify due to duplicate webhook | Shopify deduplicates with an idempotency header; on your end, check order.financial_status !== 'paid' before processing |
| WooCommerce order stuck in "Pending payment" after PayPal payment | PayPal IPN (Instant Payment Notification) must be enabled in your PayPal account; verify the IPN URL in WooCommerce → Settings → Payments → PayPal |
| Inventory not deducted until order is manually completed | Move inventory deduction to the payment confirmed webhook, not the fulfillment webhook |
| 3PL not receiving new orders automatically | Verify the fulfillment app is subscribed to the correct order status — most 3PL apps pull "Processing" (WooCommerce) or "Unfulfilled + Paid" (Shopify) |
| Order status webhooks not firing | Check webhook registration in Shopify → Settings → Notifications → Webhooks or WooCommerce → Settings → Advanced → Webhooks and verify the endpoint is returning 200 |
Related Skills
- @stripe-integration
- @inventory-tracking
- @subscription-billing
- @guest-checkout
{
"context": "Tests whether the agent correctly implements the side effect architecture: specific actions per state, side effects outside the DB transaction, error logging to a dedicated table, proper orchestration order (transition → side effects → webhook), and correct delivery timing for inventory deduction.",
"type": "weighted_checklist",
"checklist": [
{
"name": "confirmed side effects",
"max_score": 10,
"description": "The 'confirmed' status triggers all three of: sendOrderConfirmationEmail, deductInventory, and notifyFulfillmentProvider"
},
{
"name": "shipped side effects",
"max_score": 8,
"description": "The 'shipped' status triggers both: sendShippingConfirmationEmail and updateTrackingInformation"
},
{
"name": "delivered side effects",
"max_score": 8,
"description": "The 'delivered' status triggers sendDeliveryConfirmationEmail and scheduleReviewRequest with a delayDays: 3 parameter"
},
{
"name": "cancelled side effects",
"max_score": 8,
"description": "The 'cancelled' status triggers all three of: releaseInventoryReservations, initiateRefundIfPaid, and sendCancellationEmail"
},
{
"name": "Side effects outside transaction",
"max_score": 12,
"description": "runTransitionSideEffects (or equivalent) is called AFTER transitionOrder returns — it is NOT called inside a db.$transaction() block"
},
{
"name": "Error logged to sideEffectErrors table",
"max_score": 12,
"description": "Side effect failures are caught and persisted to a sideEffectErrors table (or equivalent) via a db create call, including orderId, status, and error message"
},
{
"name": "Side effect failure does not rethrow",
"max_score": 10,
"description": "A side effect failure does NOT re-throw or propagate the exception — the orchestration function continues after logging the error"
},
{
"name": "emitOrderWebhook called",
"max_score": 10,
"description": "The top-level orchestration function calls emitOrderWebhook after running side effects (not inside the transaction)"
},
{
"name": "Orchestration order",
"max_score": 10,
"description": "The orchestration function calls transitionOrder first, then runTransitionSideEffects, then emitOrderWebhook — in that sequence"
},
{
"name": "Inventory not deducted at order creation",
"max_score": 12,
"description": "Inventory deduction (deductInventory) appears only in the 'confirmed' side effects block, NOT in any 'pending' or order-creation logic"
}
]
}
Automated Order Fulfillment Actions
Problem Description
An e-commerce platform currently triggers all post-status-change actions (sending emails, deducting inventory, notifying the 3PL provider, updating tracking information) inside the same database transaction as the status update itself. This is causing two serious issues: slow email delivery is rolling back order status updates, and a single failed notification is blocking the entire order from progressing.
The engineering team wants to redesign the system so that automated actions triggered by status changes are decoupled from the database transaction. The new design should run those actions after the status change is committed, and if any action fails it should be recorded for later retry rather than crashing the order update.
The db object (Prisma-like ORM) is available in scope for any database operations. The following helper functions can be assumed to exist and are importable from a services/ directory: sendOrderConfirmationEmail, deductInventory, notifyFulfillmentProvider, sendShippingConfirmationEmail, updateTrackingInformation, sendDeliveryConfirmationEmail, scheduleReviewRequest, releaseInventoryReservations, initiateRefundIfPaid, sendCancellationEmail, emitOrderWebhook, and transitionOrder (which handles the DB update atomically).
Output Specification
Produce a JavaScript module lib/orderSideEffects.js implementing the automated actions layer. It should include a runTransitionSideEffects function and a top-level orchestration function that callers can use to perform a complete order transition.
Also produce design-notes.md at the project root explaining: (a) which actions are triggered for each order status, (b) how failures are handled, and (c) why actions are placed outside the transaction.
{
"context": "Tests whether the agent implements the order state machine and event sourcing correctly: specific state values, valid transition rules, atomic DB transactions, event log fields, timestamp milestones, and proper error handling for illegal transitions.",
"type": "weighted_checklist",
"checklist": [
{
"name": "All 8 order states defined",
"max_score": 8,
"description": "The state machine defines all 8 states: pending, confirmed, processing, shipped, delivered, cancelled, refunded, on_hold (string values)"
},
{
"name": "VALID_TRANSITIONS completeness",
"max_score": 8,
"description": "A VALID_TRANSITIONS mapping (or equivalent) covers all 8 states as keys; refunded maps to an empty array (no further transitions allowed)"
},
{
"name": "pending transitions correct",
"max_score": 8,
"description": "pending state allows exactly: confirmed, cancelled, on_hold — and NOT processing, shipped, delivered, or refunded"
},
{
"name": "shipped/delivered transitions correct",
"max_score": 8,
"description": "shipped allows exactly: delivered, refunded; on_hold allows exactly: processing, cancelled (no other transitions permitted)"
},
{
"name": "canTransition function",
"max_score": 8,
"description": "A canTransition(fromState, toState) function (or equivalent) is exported and uses the VALID_TRANSITIONS map to return a boolean"
},
{
"name": "InvalidTransitionError thrown",
"max_score": 10,
"description": "Invalid transitions throw an InvalidTransitionError (a custom or named error class/type), NOT a plain Error or generic exception"
},
{
"name": "canTransition called before update",
"max_score": 8,
"description": "The transition function calls canTransition() (or equivalent check) before performing any database write, and throws on failure"
},
{
"name": "Atomic transaction wrapping",
"max_score": 10,
"description": "The status update and the event log insertion are both executed inside a single db.$transaction() (or equivalent transactional block)"
},
{
"name": "Timestamped status field",
"max_score": 10,
"description": "The orders update sets a dynamic field named `${newStatus}At` (e.g., confirmedAt, shippedAt) to the current date/time"
},
{
"name": "Event log fields",
"max_score": 10,
"description": "Each event inserted into orderEvents includes: orderId, fromStatus, toStatus, triggeredBy, metadata/data, and createdAt"
},
{
"name": "triggeredBy default",
"max_score": 8,
"description": "triggeredBy defaults to 'system' when not explicitly provided in the metadata"
},
{
"name": "metadata default",
"max_score": 4,
"description": "The metadata/data field stored on the event defaults to an empty object {} when not provided"
}
]
}
Order Status Management Module
Problem Description
A growing online marketplace is experiencing order status corruption: orders are being marked as shipped before payment is confirmed, refunded orders are being re-processed, and customer support can't trace when or how an order reached a given state. The engineering team needs a reliable JavaScript module to manage the order lifecycle from placement through delivery.
The module must prevent invalid status changes (a refunded order should never become "shipped", for example), record a complete history of every status change for audit and debugging purposes, and capture when each milestone was reached so SLA reporting is possible.
The system uses a Prisma-like ORM accessed via a db object (you can assume it is available in scope). The relevant database tables are orders (with at minimum id and status columns) and orderEvents. You do not need to produce a working database connection — stub or mock db calls as needed.
Output Specification
Produce a JavaScript implementation in a lib/ directory that includes:
1. A state machine module defining all supported order states and the transitions that are legally permitted between them. 2. A transition function module that enforces those rules when updating an order, stores a timestamped status milestone on the order record, and appends an audit event to the event log inside a single atomic operation.
Also produce a test-transitions.js script at the project root that exercises the implementation: demonstrate at least one successful transition path, and show what happens when an illegal transition is attempted. The script should console.log its results so output is visible.
{
"context": "Tests whether the agent implements idempotent webhook handlers correctly: early-return guard on payment webhooks, triggeredBy and relevant data passed as metadata, fulfillments table updated with tracking info on shipment, and event history API returning events in ascending order.",
"type": "weighted_checklist",
"checklist": [
{
"name": "Idempotency guard present",
"max_score": 15,
"description": "The payment webhook handler checks the current order status before transitioning — if status is NOT 'pending', it returns early without calling processOrderTransition"
},
{
"name": "Early return on duplicate",
"max_score": 10,
"description": "The idempotency check uses status !== 'pending' (or equivalent), NOT a different condition such as checking a processed flag or payment ID"
},
{
"name": "orderId from paymentIntent.metadata",
"max_score": 8,
"description": "The payment webhook extracts orderId from paymentIntent.metadata.order_id"
},
{
"name": "Payment triggeredBy set",
"max_score": 8,
"description": "The payment webhook passes triggeredBy: 'stripe_webhook' (or a string identifying Stripe) in the metadata argument to processOrderTransition"
},
{
"name": "Payment metadata includes paymentIntentId and amount",
"max_score": 8,
"description": "The payment webhook passes at minimum paymentIntentId (or paymentIntent.id) and amount in the data/metadata payload to processOrderTransition"
},
{
"name": "Shipping triggeredBy set",
"max_score": 8,
"description": "The shipping webhook passes a triggeredBy string identifying the shipping provider (e.g., 'shipping_webhook', 'shipstation') to processOrderTransition"
},
{
"name": "Shipping metadata includes tracking",
"max_score": 8,
"description": "The shipping webhook passes trackingNumber and carrier in the data/metadata payload to processOrderTransition"
},
{
"name": "fulfillments table updated on shipment",
"max_score": 15,
"description": "After calling processOrderTransition, the shipping webhook also updates the fulfillments table with trackingNumber, carrier, and shippedAt (current timestamp)"
},
{
"name": "guard before missing orderId",
"max_score": 10,
"description": "The payment webhook checks that orderId is defined (from paymentIntent.metadata.order_id) and returns early if it is missing"
},
{
"name": "transition to correct states",
"max_score": 10,
"description": "Payment webhook transitions to 'confirmed'; shipping webhook transitions to 'shipped'"
}
]
}
Payment and Shipping Webhook Handlers
Problem Description
A mid-size e-commerce platform has integrated with Stripe for payment processing and ShipStation as their 3PL for fulfillment. Both services send webhook notifications when key events occur: Stripe fires payment_intent.succeeded when a customer's payment is captured, and ShipStation fires a shipment event when a carrier label is generated and the package is handed off.
The development team has observed that Stripe occasionally delivers the same payment_intent.succeeded event two or three times within minutes of each other — particularly during periods of high load. This has caused orders to be double-confirmed and inventory to be deducted twice. The ShipStation integration is new and needs to be built from scratch, capturing tracking details from the webhook payload.
A processOrderTransition(orderId, newStatus, metadata) function is available and handles the full transition pipeline (DB update, side effects, external webhooks). The db object (Prisma-like ORM) is available in scope.
Output Specification
Produce the following JavaScript files:
api/webhooks/payment.js— handler for Stripe'spayment_intent.succeededeventapi/webhooks/shipping.js— handler for shipment creation events from the 3PL
Also produce a test-idempotency.js script at the project root that demonstrates what happens when the payment webhook is called twice for the same order (i.e., that the second call is safely ignored). The script should console.log the result of each call so the behavior is visible from the output file.
{
"name": "finsi/order-processing-pipeline",
"version": "0.1.0",
"summary": "Order state machine: pending -> confirmed -> processing -> shipped -> delivered",
"skills": {
"order-processing-pipeline": {
"path": "SKILL.md"
}
}
}