You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 

7.0 KiB

Phase 8: Order Processing (BullMQ + X-API)

Status: Todo Progress: 0/15 tasks (0%) Started: - Completed: - Assigned to: -


Overview

Implement asynchronous order processing: BullMQ queue for order submission to X-API, worker for processing orders, retry logic, and BullBoard dashboard for monitoring.

Goal: Orders are reliably submitted to X-API (NAV ERP) after successful payment.


Dependencies

  • Phase 2: Database (orders table needed)
  • Phase 7: Payment (orders created after payment)
  • Docker Redis running (from docker-compose.dev.yml)
  • ⚠️ Required: X-API credentials (USERNAME, PASSWORD, BASE_URL)

Tasks

BullMQ Setup

  • Install BullMQ + ioredis

    pnpm add bullmq ioredis
    pnpm add -D @bull-board/api @bull-board/nuxt
    
  • Configure Redis connection

    • File: server/utils/redis.ts
    import { Redis } from 'ioredis'
    
    const config = useRuntimeConfig()
    export const redis = new Redis({
      host: config.redisHost,
      port: config.redisPort,
      password: config.redisPassword,
      maxRetriesPerRequest: null,
    })
    
  • Create order queue

    • File: server/queues/orderQueue.ts
    import { Queue } from 'bullmq'
    import { redis } from '../utils/redis'
    
    export const orderQueue = new Queue('x-api-orders', {
      connection: redis,
      defaultJobOptions: {
        attempts: 5,
        backoff: { type: 'exponential', delay: 2000 },
        removeOnComplete: 1000,
        removeOnFail: false,
      },
    })
    
  • Create order worker

    • File: server/workers/orderWorker.ts
    import { Worker } from 'bullmq'
    import { redis } from '../utils/redis'
    
    export const orderWorker = new Worker(
      'x-api-orders',
      async (job) => {
        const { orderId } = job.data
    
        // 1. Fetch order from DB with items and user
        const order = await db.query.orders.findFirst({
          where: eq(orders.id, orderId),
          with: { items: true, user: true },
        })
    
        // 2. Transform to X-API format
        const payload = transformOrderToXAPI(order, order.user)
    
        // 3. Submit to X-API
        const result = await submitOrderToXAPI(payload)
    
        // 4. Update order status
        await db
          .update(orders)
          .set({ status: 'completed', xapiResponse: result })
          .where(eq(orders.id, orderId))
    
        return result
      },
      {
        connection: redis,
        concurrency: 5,
        limiter: { max: 10, duration: 1000 },
      }
    )
    

X-API Client

  • Create X-API client utility

  • Implement transformOrderToXAPI function

    • File: server/utils/xapi/transformer.ts
    • Transform order from DB schema to X-API schema
    • Critical transformations:
      • Prices: EUR (Decimal) → Cents (Integer): Math.round(price * 100)
      • Dates: JavaScript Date → ISO 8601 UTC: .toISOString()
      • Line numbers: 10000, 20000, 30000... (multiples of 10000)
      • Salutation: 'male' → 'HERR', 'female' → 'FRAU', other → 'K_ANGABE'
    • See: docs/ARCHITECTURE.md: X-API Format
  • Implement submitOrderToXAPI with retry

    • Exponential backoff: 1s, 3s, 9s
    • Max 3 retries
    • HTTP Basic Auth header
    • Timeout: 30 seconds
    • Log all attempts
    • See: CLAUDE.md: X-API Pattern

API Endpoints

  • Create /api/orders/index.post.ts endpoint

    • Protected (requires auth)
    • Body: { billingAddress, paymentId }
    • Create order record in DB
    • Create order_items from cart
    • Queue order for X-API submission
    • Return: { orderId, orderNumber }
  • Create /api/orders/[id].get.ts endpoint

    • Protected (requires auth)
    • Fetch order by ID (only user's own orders)
    • Include order items with product details
    • Return: Order object

Testing

  • Test queue processing

    • Add job to queue manually: orderQueue.add('submit-order', { orderId: '...' })
    • Verify worker picks up job
    • Verify job completes successfully
    • Check BullBoard dashboard
  • Test X-API submission (mock)

    • Create mock X-API endpoint for testing
    • Submit order via queue
    • Verify transformation is correct
    • Verify Basic Auth header is present
  • Add error handling & logging

    • Log all queue events (active, completed, failed, stalled)
    • Log X-API requests/responses
    • Handle X-API errors gracefully
    • Update order status on failure

Monitoring

  • Setup BullBoard dashboard

    • File: server/api/admin/queues.ts
    import { createBullBoard } from '@bull-board/api'
    import { BullMQAdapter } from '@bull-board/api/bullMQAdapter'
    import { NuxtAdapter } from '@bull-board/nuxt'
    
    const serverAdapter = new NuxtAdapter()
    serverAdapter.setBasePath('/admin/queues')
    
    createBullBoard({
      queues: [new BullMQAdapter(orderQueue)],
      serverAdapter,
    })
    
    export default fromNodeMiddleware(serverAdapter.registerPlugin())
    
  • Test retry logic

    • Simulate X-API failure (wrong credentials or mock 500 error)
    • Verify job retries with exponential backoff
    • Verify job moves to failed after 5 attempts
    • Check retry logs
  • Document order processing

    • Document queue flow: payment → queue → worker → X-API
    • Document retry strategy
    • Document error handling and recovery
    • Document how to monitor queues

Acceptance Criteria

  • BullMQ is installed and configured
  • Redis connection is working
  • Order queue is created
  • Order worker processes jobs correctly
  • transformOrderToXAPI transforms orders correctly
  • submitOrderToXAPI submits with Basic Auth and retry logic
  • /api/orders endpoints create orders and queue jobs
  • Queue processing works end-to-end
  • X-API submissions succeed (or fail gracefully)
  • Error handling and logging are comprehensive
  • BullBoard dashboard is accessible and functional
  • Retry logic works as expected
  • Order processing is documented

Notes

  • Async Processing: Orders are queued immediately, processed in background
  • Job Timeout: 60 seconds per job (X-API timeout + overhead)
  • Concurrency: 5 jobs processed simultaneously
  • Rate Limit: 10 requests/second to X-API
  • Failed Jobs: Kept in Redis for manual inspection (not auto-deleted)

Blockers

  • ⚠️ X-API Credentials: Cannot test real submission without credentials
  • Workaround: Use mock X-API endpoint for testing, document real integration