Skip to content

Author a driver

Drivers replace policy or infrastructure without replacing commerce orchestration. A driver implements one narrow port and is resolved once by CommerceManager during provider boot.

Drivers replace policy or infrastructure without replacing commerce orchestration. A driver implements one narrow port and is resolved once by CommerceManager during provider boot.

Choose the correct port

Port Responsibility
PriceResolver Select one unit price for a variant, quantity, channel, currency, and customer context
PaymentProvider Create, confirm, capture, refund, void, verify, and normalize provider events
ShippingCalculator Quote one shipping method for a cart snapshot
ShippingEligibility Filter configured shipping methods for a cart snapshot
TaxCalculator Return line-level tax results without writing adjustments
StockAllocationStrategy Choose inventory levels; InventoryService performs guarded writes
CartTokenStrategy Read and issue anonymous cart tokens through HttpContext
JobDispatcher Dispatch and schedule idempotent job names

If your implementation needs to write cart, order, inventory, or payment rows directly, it probably belongs in a service or workflow extension instead.

Implement the interface

A shipping calculator is the smallest complete example:

import { Money } from "@adocommercekit/core";
import type { ShippingCalculator } from "@adocommercekit/core/types";

export class WeightBandShipping implements ShippingCalculator {
  readonly code = "weight-band";

  async calculate({
    cart,
    method,
  }: Parameters<ShippingCalculator["calculate"]>[0]) {
    const centsPerItem = BigInt(method.calculatorConfig.centsPerItem as number);
    const itemCount = cart.lines.reduce(
      (total, line) => total + line.quantity,
      0,
    );
    return Money.of(centsPerItem * BigInt(itemCount), cart.currency);
  }
}

Keep the driver deterministic for the same input. Do not read global request state. All request-specific values belong in the input context.

Export a factory

Commerce config stores factories so construction may use the Adonis container.

import type { DriverFactory, ShippingCalculator } from "@adocommercekit/core";

export function weightBandShipping(): DriverFactory<ShippingCalculator> {
  return async () => new WeightBandShipping();
}

Register it under a stable code:

// config/commerce.ts
import { weightBandShipping } from "#commerce/drivers/weight_band_shipping";

export default defineConfig({
  // ...
  shipping: {
    calculators: {
      "weight-band": weightBandShipping(),
    },
  },
});

A shipping-method row chooses the driver using calculator: 'weight-band' and stores serializable options in calculatorConfig.

Lifecycle rules

  • Factories resolve once and may use the application container.
  • Drivers may expose shutdown; CommerceManager.shutdown() calls lifecycle hooks during app shutdown.
  • Throw Adocommerce Kit exceptions for stable API errors. Preserve provider decline codes in exception details.
  • Never log payment tokens, provider secrets, encrypted client secrets, raw address PII, or complete webhook payloads.
  • Webhook verification must happen before normalized payloads are trusted.
  • Driver retry behavior must not undermine service idempotency keys.

Test the contract

Test the interface, not private helpers:

  1. The same input returns the same Money and currency.
  2. Unsupported config fails with a specific error.
  3. Boundary values—zero items, maximum quantity, missing address—are explicit.
  4. Provider methods forward idempotency keys.
  5. Webhook verification rejects one-byte payload and signature changes.
  6. shutdown releases open clients or timers.

For payment drivers, run both recorded fixture tests and a documented test-mode protocol. A fixture proves deterministic normalization; a live protocol catches provider drift.

Navigation

Type to search…

↑↓ navigate↵ selectEsc close