Skip to content

feat(order-forms): document the Order forms REST API and webhooks - #568

Open
toommz wants to merge 2 commits into
lago-1698from
lago-1699
Open

feat(order-forms): document the Order forms REST API and webhooks#568
toommz wants to merge 2 commits into
lago-1698from
lago-1699

Conversation

@toommz

@toommz toommz commented Aug 17, 2026

Copy link
Copy Markdown
Contributor

Stacked on #567 (lago-1698). Base is lago-1698, so review only the order-form commit; merge #567 first and this retargets to main.

Context

Second step of the Quotes documentation stack. The order form is what a customer signs: it is generated when a quote version is approved, and signing it creates the order. The REST endpoints are on main, and so are the four order_form.* webhooks from lago-api#6134. Everything here has been re-verified against merged main.

Orders get the next branch.

Changes

4 endpoints under a new order_forms tag, all gated on the premium order_forms feature flag:

Endpoint Notes
GET /order_forms filters on status[], customer_id[], number[], quote_number[], owner_id[], search_term, and created_at / expires_at ranges
GET /order_forms/{lago_id}
POST /order_forms/{lago_id}/mark_as_signed optional signed_document, execution_mode, execute_at
POST /order_forms/{lago_id}/void

4 webhooks: order_form.created, order_form.signed, order_form.expired, order_form.voided.

Notes for review

  • The signing and execution windows are bounded by the deal's own term (lago-api#6138, merged after this PR was opened). QuoteVersions::DealExpiration folds the version's end_date, every plans[].payload.endDate, and every wallet-credit / recurring-rule expirationAt into one boundary, and refuses a date landing on or after it, compared by date. one_off quotes carry none of those fields and are never bounded. The rule is described in prose rather than by its error code, matching the rest of the spec — no resource description names one.
  • One OrderFormObject serves both REST and webhooks — all four webhook services use the plain OrderFormSerializer, so there is no webhook-specific shape here, unlike the quote events in feat(quotes): document the Quotes REST API and webhooks #567.
  • signed_document is a base64 data URI, not a multipart upload (Utils::Base64File.decode). Documented with the accepted content types and the 10 MB cap from the model.
  • voided_at is set on expiry too, not just on void: ExpireService writes status: expired alongside voided_at and void_reason: expired. The field descriptions say so, since the name suggests otherwise.
  • execution_mode becomes mandatory as soon as execute_at is set, per ExecutionSettingsValidation. With neither, the order is created but never scheduled: Order.executable requires a non-null execute_at, and there is no execute endpoint.
  • Expiry is hourly, not instant. The clock job runs at *:40 and OrderForm.expirable compares calendar dates in the billing entity's timezone, so order_form.expired fires during the expiry day rather than at expires_at. Worth confirming that's the behaviour we want to promise.
  • Both lifecycle transitions cascade to the quote version (cascade_of_voided / cascade_of_expired); the descriptions name the follow-on quote.voided event.
  • OrderExecutionModeEnum is extracted here but belongs to the order too; the orders branch will reuse it.
  • redocly lint and spectral lint pass. 5 new array-params-plural warnings, same reason as feat(quotes): document the Quotes REST API and webhooks #567: the API filter params are singular while accepting both a scalar and an array.

@toommz toommz self-assigned this Aug 17, 2026
@toommz
toommz marked this pull request as ready for review August 17, 2026 20:08
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant