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:
- The same input returns the same
Moneyand currency. - Unsupported config fails with a specific error.
- Boundary values—zero items, maximum quantity, missing address—are explicit.
- Provider methods forward idempotency keys.
- Webhook verification rejects one-byte payload and signature changes.
shutdownreleases 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.