Skip to content
agovena.
agovena.
Get started
Community

Developer guides

Cart and checkout API

Cart identity, validated checkout input, payment initiation, and idempotency without client-side totals.

On this page

Cart identity comes first

The API uses TokenCartRepository, not the storefront's session cart repository. When Laravel resolves a User on the request, the API cart is keyed by that user's ID. Otherwise it is keyed by the X-Cart-Token header.

A guest token is 64 lowercase hexadecimal characters. Missing or malformed input produces a new random token. Cart responses return it both as data.token and the X-Cart-Token response header. Preserve that value for subsequent guest requests. It is a cart access credential, not a Sanctum login token. Cart contents are stored in cache for seven days after persistence and disappear if that cache entry is cleared.

Guest and authenticated carts do not automatically merge. The public cart routes do not themselves run auth:sanctum; adding a bearer header is not a documented guest-to-customer transfer. Checkout does require auth:sanctum and reads the resolved user's cart. A guest X-Cart-Token therefore does not prove that checkout sees the same contents. For an authenticated first-party client, keep the same supported session while calling the API cart and checkout. For a bearer-only client, verify the resolved cart context in an integration test before building a purchase flow around it. The current route set has no cart-claim endpoint.

The ordinary storefront cart is separate as well. A product added on a web page is not automatically present in the API cart. Source: repository binding.

Add, update, and remove lines

Operation Validated input
GET /api/v1/cart No body
POST /api/v1/cart Required integer product_id; nullable integer quantity from 1 to 99, default 1; nullable array selections
PATCH /api/v1/cart/{lineKey} Required integer quantity from 0 to 99
DELETE /api/v1/cart/{lineKey} No body

A quantity of zero removes a line. Use the returned line_key, not a product ID, in update and delete paths. Distinct option selections can create distinct lines for one product. Use product option keys and choice values from the catalog resource; the cart service validates the configuration and calculates prices.

Guest request examples, using installation-specific IDs and the returned token:

bash
curl --request POST 'https://shop.example.test/api/v1/cart' \
  --header 'Accept: application/json' \
  --header 'Content-Type: application/json' \
  --data '{"product_id":42,"quantity":1,"selections":{}}'

curl 'https://shop.example.test/api/v1/cart' \
  --header 'Accept: application/json' \
  --header "X-Cart-Token: $CART_TOKEN"

curl --request PATCH "https://shop.example.test/api/v1/cart/$LINE_KEY" \
  --header 'Accept: application/json' \
  --header 'Content-Type: application/json' \
  --header "X-Cart-Token: $CART_TOKEN" \
  --data '{"quantity":2}'

curl --request DELETE "https://shop.example.test/api/v1/cart/$LINE_KEY" \
  --header 'Accept: application/json' \
  --header "X-Cart-Token: $CART_TOKEN"

Every operation returns the updated cart envelope:

json
{
  "data": {
    "token": "<64-character-cart-token>",
    "item_count": 1,
    "requires_shipping": false,
    "subtotal_amount": 1200,
    "currency": "EUR",
    "lines": [{
      "line_key": "<returned-line-key>",
      "product_id": 42,
      "label": "Example product",
      "quantity": 1,
      "unit_amount": 1200,
      "line_total_amount": 1200,
      "currency": "EUR",
      "selections": []
    }]
  }
}

subtotal_amount and currency may be null for an empty cart. Treat selections as the server's normalized map; an empty PHP array serializes as []. Amounts are minor units. Never calculate an authoritative order total from this illustrative response.

Source: AccountController.

Discover requirements

GET /api/v1/checkout/requirements reads the current API cart and returns:

Field under data Shape
requirements Array of requirement IDs
requires_shipping Boolean
payment_methods Array of {id, label, gateway_id?, icon?}

Core contributes billing, payment, and review requirements. Other contributors add capability-dependent requirements. Payment methods are discovered from the active gateway registry, so do not hard-code a provider list or assume the method ID always equals a provider's gateway ID.

bash
curl 'https://shop.example.test/api/v1/checkout/requirements' \
  --header 'Accept: application/json' \
  --header "X-Cart-Token: $CART_TOKEN"

This call does not create an order, reserve a payment attempt, or return a shipping-rate catalog. Development instant-pay is offered only under its explicit non-production condition and only when no real gateway methods are available. See AvailablePaymentMethods.

Place an order

POST /api/v1/checkout requires authentication and orders.create for a bearer token. It uses the current customer's name, email, and ID, not client-supplied customer identity. The request fields are:

Field Rule
billing Required array
billing.name, billing.line1 Required strings, maximum 255
billing.city Required string, maximum 120
billing.postal_code Required string, maximum 20
billing.country Required string, exactly two characters
billing.company, billing.line2 Nullable strings, maximum 255
billing.region Nullable string, maximum 120
billing.phone Nullable string, maximum 40
payment_method Nullable string, maximum 40; use a discovered method
idempotency_key Nullable string, maximum 64
shipping Nullable address array, converted to AddressData
shipping_same_as_billing Optional boolean, defaults to true
shipping_method_id Nullable integer
discount_code Nullable string, maximum 40
custom_properties Nullable array

The controller validates shipping as an array without duplicating all nested billing rules. This does not make arbitrary shipping data valid: AddressData and PlaceOrder still apply domain behavior. Products, totals, currency, referral fields, and shipping quote keys are not accepted checkout overrides in this controller.

The following is an illustrative checkout-request.json. It assumes the authenticated API cart is already populated and configured. It is not a guest-cart transfer example.

json
{
  "idempotency_key": "customer-checkout-example-01",
  "billing": {
    "name": "Alex Example",
    "line1": "Example Street 1",
    "city": "Brussels",
    "postal_code": "1000",
    "country": "BE"
  },
  "shipping_same_as_billing": true,
  "payment_method": "<discovered-method-id>",
  "custom_properties": {}
}
bash
curl --request POST 'https://shop.example.test/api/v1/checkout' \
  --header "Authorization: Bearer $AGOVENA_TOKEN" \
  --header 'Accept: application/json' \
  --header 'Content-Type: application/json' \
  --data-binary @checkout-request.json

The response is an Order resource, with items, payment, invoices, and credit-note summaries. Order placement is not necessarily payment completion. Empty or misconfigured carts and failed domain requirements produce validation errors.

Use one idempotency key per intended checkout. PlaceOrder looks for an existing owner-scoped order before evaluating a new cart and can resume it. Reusing the key is not a way to amend that order with a new body. This controller reads the key from JSON; unlike payment initiation, it does not copy an Idempotency-Key header into the body.

Start a gateway payment

POST /api/v1/orders/{order}/pay requires orders.pay and an owned order. Required fields are gateway (string, maximum 40), return_url, and cancel_url (URLs, maximum 500 each). Optional idempotency_key is a string of at most 64 characters. If the body does not contain the key, the controller accepts the Idempotency-Key header.

Both redirect origins must be in agovena.payments.return_url_origins, configured with AGOVENA_PAYMENT_RETURN_ORIGINS and defaulting to APP_URL. URLs with credentials or fragments are rejected. Origin matching includes the scheme and an explicit port.

bash
curl --request POST "https://shop.example.test/api/v1/orders/$ORDER_ID/pay" \
  --header "Authorization: Bearer $AGOVENA_TOKEN" \
  --header 'Accept: application/json' \
  --header 'Content-Type: application/json' \
  --header 'Idempotency-Key: customer-payment-example-01' \
  --data '{"gateway":"<registered-gateway-id>","return_url":"https://shop.example.test/payment-return","cancel_url":"https://shop.example.test/payment-cancel"}'

Use return and cancel routes your client actually implements. The example paths are not built-in Agovena routes. The response has integer data.attempt_id, string data.status for the attempt, nullable string data.redirect_url, and nullable string data.payment_status for the order's payment. Domain validation raised by StartOrderPayment is wrapped as 422 payment_failed; earlier request or URL validation remains validation_error.

Follow the provider redirect when present, but obtain final state from the order or verified payment webhook processing. Keep checkout and payment idempotency keys separate. Do not issue a fresh payment attempt automatically after an ambiguous network timeout without reconciling the previous attempt.

Source and regression checks

Add an explicit guest-to-authenticated cart test for your integration. The existing public-cart persistence test does not establish a complete bearer-only purchase journey.

Search documentation

Search guides, commands and API endpoints

What are you looking for?

Documentation