Skip to Content
Packages@loop/commerce

@loop/commerce

Framework-agnostic commerce package with cart, checkout, payment engine, subscription lifecycle, and dunning system.

Installation

pnpm add @loop/commerce

Overview

@loop/commerce extracts 47,700 lines of production-tested commerce logic from loopbio-v2 into a reusable package. Built for multi-brand e-commerce at scale.

Key modules:

  • Cart & Checkout - Multi-tier cart state machine with Basis Theory integration
  • Payment Engine - Stripe + Basis Theory with payment ledger and webhooks
  • Subscription Engine - Lifecycle management with 27-attempt dunning system
  • Order Management - Order creation, fulfillment, tracking

Subpath Exports

import { createCart, checkoutFlow } from '@loop/commerce/cart' import { processPayment, handleWebhook } from '@loop/commerce/payment' import { createSubscription, processDunning } from '@loop/commerce/subscription'

Cart & Checkout

Creating a Cart

import { createCart } from '@loop/commerce/cart' const result = await createCart({ customerId: 'cus_123', items: [ { productId: 'prod_abc', quantity: 2, price: 4900 } ], metadata: { source: 'web', affiliateCode: 'PARTNER10' } }) if (result.ok) { console.log('Cart created:', result.data.id) }

Checkout Flow

import { checkoutFlow } from '@loop/commerce/cart' const result = await checkoutFlow({ cartId: 'cart_123', paymentMethodId: 'pm_xxx', shippingAddress: { line1: '123 Main St', city: 'Austin', state: 'TX', zip: '78701' } })

Payment Engine

Processing Payments

import { processPayment } from '@loop/commerce/payment' const result = await processPayment({ customerId: 'cus_123', amount: 9900, // $99.00 in cents currency: 'usd', paymentMethodId: 'pm_xxx', metadata: { orderId: 'order_456', subscriptionId: 'sub_789' } })

Webhook Handling

import { handleWebhook } from '@loop/commerce/payment' // In your API route (e.g., /api/webhooks/stripe) export async function POST(req: Request) { const sig = req.headers.get('stripe-signature') const body = await req.text() const result = await handleWebhook({ body, signature: sig!, webhookSecret: process.env.STRIPE_WEBHOOK_SECRET! }) if (!result.ok) { return new Response('Webhook error', { status: 400 }) } return new Response('Success', { status: 200 }) }

Subscription Engine

Creating Subscriptions

import { createSubscription } from '@loop/commerce/subscription' const result = await createSubscription({ customerId: 'cus_123', priceId: 'price_monthly', interval: 'month', intervalCount: 1, metadata: { tier: 'plus' } })

Dunning System

The package includes a battle-tested 27-attempt dunning system over 14 days:

Four phases:

  • Phase 1: 6 retries @ 4h intervals (hours 4, 8, 12, 16, 20, 24)
  • Phase 2: 6 retries @ 8h intervals (hours 32, 40, 48, 56, 64, 72)
  • Phase 3: 8 retries @ 12h intervals (hours 84-168)
  • Phase 4: 7 retries @ 24h intervals (hours 192-336)
import { processDunning, getDunningPhase } from '@loop/commerce/subscription' const phase = getDunningPhase(failureCount) // 0-3 const shouldEmail = shouldSendDunningEmail(failureCount) // Process dunning (called automatically via webhook) await processDunning({ subscriptionId: 'sub_123', failureReason: 'insufficient_funds' })

Hard decline codes (instant cancel):

  • stolen_card, lost_card, fraudulent
  • expired_card, invalid_number
  • restricted_card, card_not_supported

Environment Variables

# Stripe STRIPE_SECRET_KEY=sk_xxx STRIPE_WEBHOOK_SECRET=whsec_xxx # Basis Theory (tokenization) BASIS_THEORY_API_KEY=key_xxx

Architecture

Framework-agnostic:

  • Pure TypeScript functions
  • No framework coupling
  • Bring your own database
  • Composable utilities

Production-ready:

  • Full Zod schemas for type safety
  • Comprehensive error handling
  • Vitest test suites
  • Battle-tested in loopbio-v2 (2+ years)

Migration from loopbio-v2

If you’re migrating from the loopbio-v2 implementation:

// Old (loopbio-v2) import { createCart } from '@/lib/commerce/cart' // New (@loop/commerce) import { createCart } from '@loop/commerce/cart'

All function signatures are identical - just change the import path.

Used By

@loop/commerce is used by applications that handle e-commerce functionality:

Source

Extracted from loopbio-v2 production codebase: