Skip to content

Troubleshooting

Diagnose installation, checkout, webhook, regional provider, and admin failures.

Start with the structural diagnostic:

node ace commerce:doctor

Then reproduce the failing request with the same database, configuration, headers, cookies, and payment mode as the application. The generated storefront returns commerce failures as:

{
  "error": {
    "code": "E_INSUFFICIENT_STOCK",
    "message": "Insufficient stock",
    "details": {}
  }
}

Use error.code for program logic. Messages and details are diagnostic context.

Installation and boot

commerce:doctor reports missing tables

Run:

node ace migration:status
node ace migration:run
node ace commerce:doctor

If migrations were already applied under a different tablePrefix, restore the matching configuration. Do not edit applied migration files or change the prefix to hide the error. A prefix change requires a host migration that renames the schema.

Commerce services cannot be resolved

Confirm adonisrc.ts contains the core provider and command entrypoint. Re-run the core configurator in development if they are absent:

node ace configure @adocommercekit/core

Review and commit the changes. Do not run the configurator automatically in production.

Session cart tokens require @adonisjs/session middleware

The generated config uses sessionCartToken(). Install and configure @adonisjs/session, ensure session middleware runs on the commerce routes, and preserve the cookie in the client.

For a stateless storefront, use headerCartToken() in config/commerce.ts. The cart response contains the opaque token; send it back in the configured header. Never accept a cart database ID as the token.

Unknown payment, shipping, or other driver

A configured default or database record references a key that is not registered in config/commerce.ts. Check:

  • payments.default exists in payments.methods;
  • the checkout payment.method is registered;
  • shipping methods reference a key in shipping.calculators;
  • every driver entry is a factory such as manualPayment(), not a partial object.

Restart Adonis after changing configuration because drivers are resolved and cached at boot.

Queue integration fails at boot

jobs.dispatcher: 'queue' dynamically loads @adonisjs/queue. Install and configure a compatible version or switch back to inline for a single-process development environment. Run commerce:doctor after the change.

Storefront requests

/api/commerce/* returns 404

The storefront package publishes, rather than mounts, routes. Confirm:

  • node ace configure @adocommercekit/storefront-api completed;
  • start/routes/commerce.ts exists;
  • adonisrc.ts preloads #start/routes/commerce;
  • node ace list:routes includes names beginning with commerce..

If you moved or renamed published files, update their imports and preload entry as ordinary Adonis code.

A new cart appears on every request

The client is not returning the cart credential. With the default session strategy, use one cookie jar:

curl -c .cart-cookie -b .cart-cookie http://localhost:3333/api/commerce/cart

In a browser, ensure credentials and cookie attributes permit the request origin. Behind HTTPS or a proxy, check Adonis session cookie security and proxy trust.

With headerCartToken(), read the token from the cart response and return it in the configured header.

Mutating routes fail CSRF validation

Adocommerce Kit does not disable CSRF for ordinary cart or checkout requests. For a browser session, expose Shield’s XSRF cookie and send X-XSRF-TOKEN. For a token-authenticated API, define an explicit alternative CSRF policy in the host application.

Only the external payment webhook path should normally be excluded from CSRF.

Validation rejects an address

countryCode must be a two-letter code. The published validator requires postalCode for countries in its configured list and limits address field lengths. Because the validator is host-owned, update it deliberately if the business supports a different address contract.

Rate limits are inconsistent between instances

The generated limiter is process-local. Each application instance has separate counters. Replace the published middleware with a shared limiter or enforce the same policy at the gateway. See HTTP scaling and rate limits.

Catalog, cart, and checkout errors

Code Meaning Corrective action
E_PRICE_NOT_FOUND No applicable price for the variant, channel, currency, customer group, quantity, and current time Add or correct a price list/price and channel assignment; do not invent a fallback amount in the controller
E_CART_NOT_ACTIVE The cart is checking out, completed, abandoned, or otherwise unavailable findOrCreateFromContext replaces terminal carts automatically; when passing carts explicitly, create a new one
E_CART_PRICES_CHANGED Price snapshots changed between review and checkout Show the returned diff, refresh totals, and ask the customer to confirm before retrying with a new idempotency key
E_LINE_QUANTITY Quantity is outside service limits Keep API validation and carts.guardMaxLineQuantity aligned
E_INSUFFICIENT_STOCK Allocation cannot satisfy the requested quantity Refresh availability, reduce quantity, or apply the intended backorder policy
E_SHIPPING_NOT_ELIGIBLE The selected method no longer matches the cart/address Re-list shipping methods and require a new selection
E_STATE_TRANSITION A stale or invalid domain transition was attempted Reload the record and apply an allowed transition; do not update state columns directly
E_PAYMENT_DECLINED Provider rejected the payment Use details.declineCode to choose customer copy; do not retry fraud or authentication failures blindly
E_PAYMENT_PROVIDER Provider request or response failed Correlate logs with the provider, preserve idempotency, and retry only when provider semantics allow it
E_IDEMPOTENCY_CONFLICT One key was reused with a different request payload Generate a new key for the changed intent; never mutate and replay the old request
E_WORKFLOW_FAILED Checkout failed and compensation ran Inspect the failing step and any compensationErrors; reconcile stock and payment state before manual replay

Stripe and webhooks

Stripe checkout returns payment_not_authorized

The PaymentIntent did not reach authorized or captured. Inspect its provider state and Stripe logs. Common causes are a missing/invalid PaymentMethod ID, a decline, or requires_action.

The generated storefront controller does not complete 3DS/SCA. See Strong Customer Authentication.

E_WEBHOOK_SIGNATURE

Check all of the following:

  • STRIPE_WEBHOOK_SECRET belongs to this exact endpoint and mode;
  • local Stripe CLI forwarding uses the secret printed by the current stripe listen process;
  • the proxy and middleware preserve the raw body bytes;
  • the Stripe-Signature header reaches Adonis unchanged;
  • server time is synchronized and the event is within the configured tolerance.

Do not log the webhook secret or bypass verification to make a delivery succeed.

Stripe delivers repeatedly even though the endpoint returns 200

Stripe retries can overlap and Adocommerce Kit deduplicates on provider event ID. Confirm the webhook-event row is recorded and inspect whether it is handled, unhandled, or failed. A verified unknown event intentionally returns 200; add support only if that event should change domain state.

Deferred payments and regional providers

An order stays in pending_payment forever

Deferred providers settle from a provider notification or a scheduled expiry sweep, not from the checkout response. Confirm that jobs.dispatcher is 'queue' with a running worker, that the expiry job is scheduled, and that the provider’s webhook actually reaches the application. commerce:doctor fails in production when a deferred driver is registered without the queue dispatcher.

commerce:doctor reports a deferred driver without retrieveIntent

Every provider whose capabilities().flow is deferred must implement retrieveIntent so status can be re-queried independently of webhook delivery. A custom driver that reports deferred without it will fail doctor by design; see authoring a driver.

Midtrans notifications are rejected or settle the wrong order

Midtrans signatures use its exact SHA-512 formula over order ID, status code, gross amount, and server key. A mismatch usually means the wrong server key, the wrong environment, or a re-serialized gross amount. After verifying, the provider re-queries current transaction status before returning a normalized event, so a stale or spoofed notification body cannot settle an order on its own.

Xendit callbacks fail verification

Xendit does not use Midtrans signature rules. It authenticates callbacks with a constant-time x-callback-token comparison and its API with Basic secret-key auth. Confirm the token matches the configured endpoint and that the pinned 2024-11-11 Payment Requests version is still current for your account.

A refund is rejected as manual-only

MidtransManualRefundRequiredError and KodeUnikManualRefundRequiredError are expected for channels without automatic refunds. Midtrans offers automatic refunds only for QRIS and GoPay; every other channel, and every kode-unik payment, requires an operator-recorded manual transfer reference. Check MIDTRANS_REFUND_CAPABILITIES before offering a refund in the UI.

Kode-unik allocation throws a RangeError

Visible codes run 001999 per receiving account, so at most 999 leases can be active at once. Exhaustion raises All kode-unik values are active for receiving account <id>. Shorten the lease window (expiresAfterMinutes, default 1440), add receiving accounts, or move high volume to a gateway. Allocating twice for the same reference returns the existing lease rather than a second code.

Biteship tracking webhooks always fail

Biteship verification fails closed: without a host-supplied webhookVerifier and a signature header, the request is rejected. Supply a verifier that matches your pinned Biteship contract instead of disabling verification.

Shipping quotes are missing, stale, or rejected at checkout

Quote identity covers parcel values, weight, dimensions, destination, COD, insurance, currency, and provider options, and the rate cache TTL is capped at 60 seconds. A changed cart therefore invalidates a previous quote, and checkout rejects a selection whose signed quote token no longer matches. Re-list quotes and require a fresh selection. RajaOngkir reports waybill creation, fulfillment, COD, and insurance as unsupported rather than emulating them, so a host that needs those must use Biteship.

Indonesian address or region lookups return nothing

No region rows ship with @adocommercekit/id. Import a licensed source with commerce:id:regions-import --file. The importer rejects a non-HTTPS source or license URL and any expectedChecksum that is not a 64-character SHA-256 digest.

WhatsApp messages are never delivered

Sends require a pinned Graph API version, an approved template, and current purpose consent checked both before enqueue and before send; default configuration is disabled. WhatsAppTemplateNotApprovedError and retry exhaustion call only a host-configured fallback notifier, and there is no assumed email fallback. Missing consent is a correct refusal to send, not a bug — see the PDP operations runbook.

COD remittance rows do not settle

commerce:id:cod-import is idempotent and never guesses. It reports settled, duplicates, unmatched, ambiguous, and invalid counts with one warning per problem row. Rows that do not match exactly one order must be reconciled by hand; re-running the same file settles nothing twice.

Admin panel

The panel throws you back to the login route every few clicks

commerceAdminAuth redirects a page load to redirectTo whenever the session carries no authenticated actor. Mid-session that almost always means the browser handed back a session id the app never issued, because cookies are scoped by host and path but not by port. Every AdonisJS app reachable on the same hostname writes adonis-session at Path=/ by default, so a second copy of your app on another port, or an unrelated project on localhost, silently replaces the panel’s session id. The panel cannot decrypt it, mints a fresh empty session, and the next click is a redirect to the login route.

Give the app its own session cookie name in config/session.ts — keying it on the listen port gives every local instance a distinct one:

cookieName: `myapp_session_${env.get('PORT')}`,

Shield’s XSRF-TOKEN cookie name is not configurable, so a co-located app can still cost you one E_BAD_CSRF_TOKEN on the next mutation. The panel re-issues the token on every response, so a reload clears it.

Panel mutations lose their flash notice, or every request answers 431

SESSION_DRIVER=cookie serialises the whole session into one cookie, and the panel’s flash payload is unbounded: a validation failure on a 200-line shipment renders a 35KB Set-Cookie. Browsers discard any cookie past 4096 bytes, so that response’s session write — flash included — is dropped, and a client permissive enough to store it sends a header the server answers with 431 Request Header Fields Too Large until the cookie expires. The cookie store also names the data cookie after the session id, so every id change strands the previous cookie in the browser for good. Run the panel on database or redis.

Generated-file upgrades

commerce:upgrade reports a conflict

The host file differs from its recorded publication base. Adocommerce Kit leaves it untouched and stages incoming content plus merge instructions under .commercekit/incoming.

Merge deliberately:

  1. compare host, base, and incoming versions;
  2. preserve application-specific behavior and adopt relevant upstream security or contract changes;
  3. run typecheck and the affected HTTP smoke;
  4. update and commit .commercekit/publications with the resolved host file.

Do not delete .commercekit/publications; without the base, future upgrades cannot distinguish a safe update from a host modification.

Generated API docs do not match exports

The API page is generated from TypeScript entrypoints. From the workspace root run:

pnpm docs:build

Do not edit docs/src/content/docs/api/generated.mdx by hand.

Still unresolved

Capture these facts before opening an issue:

  • exact package versions and package manager lockfile;
  • Node.js, AdonisJS, Lucid, database, and queue versions;
  • database dialect;
  • commerce:doctor output;
  • commerce error code and sanitized details;
  • minimal configuration and reproduction;
  • whether published files were modified;
  • sanitized provider request/event identifiers.

Report issues through the GitHub issue tracker without secrets, payment tokens, card data, or customer PII.

Navigation

Type to search…

↑↓ navigate↵ selectEsc close