Skip to main content

External Implementation: Validate Order Before Payment (via API function)

Who is this guide for?

This guide is intended for developers building external checkout experiences. If you are using the standard Checkout Widget without a custom checkout flow, this validation is performed automatically by Checkout and no additional implementation is required.

External implementations may restore an order after refresh or when the customer returns later. Without validation, they could continue to payment even though required information is still missing. The Ventrata.validate() API function solves that problem.


Recommended Approach

Ventrata provides the Ventrata.validate() API for validating whether an order is ready to proceed to payment.

For most external implementations, this is the recommended approach. It reproduces Checkout's validation behaviour without requiring you to maintain your own validation logic.

πŸ“— TIP

If you cannot use Ventrata.validate(), see External Implementation: Validate an Order Before Payment (via pseudocode) for a reference implementation of Checkout's validation logic.


Using Ventrata.validate()

Ventrata.validate() provides the same order-completion validation used by Checkout for external implementations. Call it before allowing the customer to continue to payment.

The Checkout script adds the Ventrata.validate() function to the existing window.Ventrata object.

await window.Ventrata.validate(input);

πŸ“— TIP

The function is asynchronous. Always use await or .then().

Use this API for:

  1. A fully custom checkout experience

  2. A custom cart using Ventrata orders

  3. A hybrid checkout using both Checkout and custom pages

  4. A flow that restores saved orders before payment

πŸ“’ NOTE

A website using the Ventrata Checkout flow does not need to call the Ventrata.validate() API on every Checkout page β€” it already runs its own series of checks.


Recommended flow

Validate the order immediately before payment, and again whenever the order has been updated.

  1. Create or update an order.

  2. Save the order ID.

  3. Before payment, call Ventrata.validate().

  4. Check result.valid:

    • if true - continue to the next implemented step

    • if false - read the items in result.issues


    πŸ“’ NOTE

    Also run validation after restoring an order, as the order may not be complete.


  5. Ask the customer to complete the missing information.

  6. Update the order.

  7. Call Ventrata.validate() again.


When to Use the API

The Ventrata.validate() function operates in two modes:

Full Order Mode

Ventrata.validate({ order });

  • Use when the full order object is loaded on the page.

  • Ventrata.order can be null. This happens before an order exists or after it was cleared. The result will not be valid and will say that the order is missing.

Name

Required

Description

order

Yes

the full Ventrata order object to check

πŸ“’ NOTE

Do not pass Ventrata.order.id in this field. Use Order ID mode when only the ID is available.

πŸ“˜ EXAMPLE

const result = await Ventrata.validate({
order: Ventrata.order,
});


Order ID Mode

Ventrata.validate({ orderId, apiKey, env });

  • Use when the page only saved the order ID.

  • The API loads the latest order from Ventrata and then checks it.

  • The name is orderId with a lowercase d. Some older Checkout configuration examples use orderID, but that is not the name used by this API.

Name

Required

Description

orderId

Yes

ID of the order to load or check

apiKey

Yes

API key used to load the order

πŸ“’ NOTE

The API key must be able to read the order in the selected environment. A wrong key or wrong environment returns the normal load failure result.

env

No

"test" (default) / "live"

πŸ“’ NOTE

For production, always pass env: "live".

πŸ“’ NOTE

If env is not present, the API uses the test environment.

πŸ“˜ EXAMPLE

const result = await Ventrata.validate({
orderId: "019f6d01-c1da-71fb-9d00-2398de9f2ca1",
apiKey: "YOUR_API_KEY",
env: "live",
});

πŸ“’ NOTE

Do not provide both a full order and orderId in the same call. If a real value is present, the full order mode is used. The order ID is ignored.

JavaScript objects sometimes contain order: undefined. This is treated as if order was not present. A valid orderId can still be used in that case.


What the API Checks

Ventrata.validate() validates the latest saved order. It checks for missing information, including:

  • required availability for package includes

  • required visible questions for a booking

  • required visible questions for a unit

  • required visible questions for a package booking

  • required visible questions for a package unit

  • required contact fields for units and package units

  • required pickup and dropoff points when the saved availability says they are required

  • required visible questions for the whole order

  • required contact fields for the whole order

For required questions, these values count as missing:

  • null;

  • undefined;

  • an empty string;

  • a string that only contains spaces.

Hidden Questions

A hidden dependent question does not block the order. It is only checked when its rules make it visible.

Contact Fields

For contact fields, empty text and empty lists count as missing.

Packages

An optional package include does not need an availability. A required include does.

Cancelled top-level bookings are ignored.

Multi-Date Packages

A multi-date package is one top-level booking with one or more package bookings inside it. The package bookings represent the included products.

Example shape:

order
└── top-level booking
β”œβ”€β”€ package booking A
└── package booking B

The API can find a required package include that still has no saved date or time. It returns package-availability for that case.

This matters because a multi-date order can already exist while one of its required includes still has no selected date or time.

A saved order may already contain a pickup or dropoff point before the customer manually changes or confirms it.

πŸ“— TIP

If your implementation supports multi-date packages, see How to Create Multi-Date Packages.

Multibooking

Multibooking means one order can contain several top-level bookings.

Example shape:

order
β”œβ”€β”€ booking A
β”‚ └── optional package bookings
└── booking B
└── optional package bookings

The API checks every active top-level booking. One result can contain issues for more than one booking.

Each booking issue contains the parent bookingUuid. This lets the external implementation identify which booking needs attention.

The API does not create child orders for multibooking.

πŸ“— TIP

If your implementation supports multibooking, see How to Use the Multibooking Feature.

Contact Requirements and Quotes

The API trusts the required contact fields saved on the order and its units. It does not load the supplier's Checkout configuration to invent extra rules.

For example, quotesAllowed means that saving a quote is available. It does not mean that email must be required for every normal order.

‼️ IMPORTANT

Ventrata.validate() checks saved data, not the page the customer is currently viewing. The returned step indicates the type of missing information, not the last Checkout page the customer visited.


Result Reference

The API returns all supported issues it finds. It does not return only the first issue. The list is not sorted as a public page order, therefore the list of issues does not correspond to Checkout pages in the same order.

Use valid First

Check result.valid first. Only inspect result.issues when validation fails.

const result = await Ventrata.validate({ orderId, apiKey, env: "live" });

if (result.valid) {
continueToPayment();
} else {
showMissingInformation(result.issues);
}

πŸ“’ NOTE

continueToPayment() and showMissingInformation() are examples. This API does not add those functions.

An external implementation can follow its own flow and choose which issue to show first.

type OrderCompletionValidationResult = 
| {
valid: true;
issues: [];
}
| {
valid: false;
issues: OrderCompletionIssue[];
};

type OrderCompletionIssue = {
reason: string;
step?: OrderCompletionStep;
bookingUuid?: string;
};

type OrderCompletionStep =
| "package-availability"
| "booking-questions"
| "contact-details"
| "pickup"
| "dropoff"
| "order-questions";

Types of validation results:

Complete order

{
valid: true,
issues: []
}

The API did not find a supported completion problem in the saved order. The order can still expire, inventory can change, and the backend can still reject payment or confirmation for another reason.


Incomplete order

{
valid: false,
issues: [
{
step: "booking-questions",
bookingUuid: "booking-uuid-123",
reason: 'Required booking question "question-id-123" is missing.'
}
]
}

An issue can contain these fields:

Name

Description

step

The type of missing information

bookingUuid

Top-level booking that needs attention, not always present

reason

Human-readable detail for support and debugging


More than one issue

{
valid: false,
issues: [
{
step: "booking-questions",
bookingUuid: "booking-uuid-123",
reason: 'Required booking question "question-id-123" is missing.'
},
{
step: "contact-details",
bookingUuid: "booking-uuid-123",
reason: 'Required unit contact field "phoneNumber" is missing.'
},
{
step: "order-questions",
reason: 'Required order question "question-id-456" is missing.'
}
]
}

An order can have several missing values, which are returned together.

Every result contains the following:

  • valid is always present.

  • issues is always present.

  • When valid is true, issues is always empty.

  • When valid is false, the current implementation returns at least one issue.

  • Every issue has reason.

  • A normal missing-information issue has step.

  • A booking-level issue also has bookingUuid.

  • An order-level issue has no bookingUuid.

  • An input, loading, or bad-data issue can contain only reason.


Step Values

step identifies the category of missing information. It is intended to help your application decide how to recover from validation failures. It is not a Checkout route and cannot be used to open a Checkout page directly.

As a result, the public API does not include a function that opens a specific Checkout page. The external implementation must connect each value to its own recovery flow.

Step

Definition

"package-availability"

A required availability still needs to be selected

"booking-questions"

A required booking, unit, or package booking question needs an answer

"contact-details"

A required contact field still needs a value

"pickup"

A booking or package booking requires pickup selection

"dropoff"

A booking or package booking requires dropoff selection

"order-questions"

A required order-level question still needs an answer


Current Issue Messages

These are all current missing-information message forms. Text inside <...> is replaced with a real UUID, question ID, or field name.

‼️ IMPORTANT

Use valid, step, and bookingUuid in your application logic. The reason field is intended for people and may change between releases.

step

reason

bookingUuid

package-availability

Package booking "<package-booking-uuid>" requires availability.

Parent booking

pickup

Booking "<booking-uuid>" requires pickup selection.

The referenced booking

pickup

Package booking "<package-booking-uuid>" requires pickup selection.

Parent booking

dropoff

Booking "<booking-uuid>" requires dropoff selection.

The referenced booking

dropoff

Package booking "<package-booking-uuid>" requires dropoff selection.

Parent booking

booking-questions

Required booking question "<question-id>" is missing.

Parent booking

booking-questions

Required unit question "<question-id>" is missing.

Parent booking

booking-questions

Required package booking question "<question-id>" is missing.

Parent booking

booking-questions

Required package unit question "<question-id>" is missing.

Parent booking

contact-details

Required unit contact field "<field-name>" is missing.

Parent booking

contact-details

Required package unit contact field "<field-name>" is missing.

Parent booking

contact-details

Required order contact field "<field-name>" is missing.

Not present

order-questions

Required order question "<question-id>" is missing.

Not present


What the API Does Not Do

The API does not:

  • Create an order

  • Update or fix an order

  • Submit or confirm payment

  • Guarantee that payment will succeed

  • Reserve inventory again

  • Tell the caller which screen was last open

  • Prove that a preselected pickup or dropoff was manually confirmed

  • Open Checkout on a returned step

  • Return an ordered list of pages

  • Replace final backend validation

  • Check every possible business rule

  • Check required consent checkboxes that only exist in local form state

  • Infer future save-as-quote intent from quotesAllowed

  • Load general Checkout configuration such as productID, multibooking, or feature flags

Consent checkboxes and other rules that are not stored on the order need a separate follow-up design. The validator cannot read data that is not in its input or in the loaded order.


Input, Loading, and Data Error Messages

The API returns an invalid result instead of leaving the caller with an uncaught error.

This is a list of current non-completion error messages:

Scenario

Example reason

No usable input

Either order or orderId is required

orderId is present but the API key is missing

API key is required

env is neither "test" nor "live"

Environment must be "test" or "live"

The order is null

Order is missing.

The order has no bookings list

Order bookings are missing.

The order has no usable ID

Order ID is missing.

Nested order data has a bad shape

Order data is invalid.

The order request or response fails

Unable to load order

πŸ“’ NOTE

For these errors, an issue may contain only reason, without step or bookingUuid. The promise still resolves with { valid: false, issues: [...] }, so you do not need to use try/catch for normal validation results.

A general try/catch is still good practice around the payment flow to handle unexpected runtime, network, or payment errors.


Troubleshooting

Ventrata.order is null or returns an invalid result "Order is missing."

Some Checkout pages can appear before the first order is created. The exact pages depend on the product and flow. Pickup and dropoff can be part of this early flow.

Before the order exists:

  • Ventrata.order can be null;

  • there is no orderId to load;

  • Ventrata.validate({ order: Ventrata.order }) returns an invalid result with Order is missing.;

  • the API cannot say how far the customer moved through those early pages.

This does not always mean the customer made an error. It can simply mean that there is no saved order to check yet.

An external implementation must keep its own page state before order creation.

The API becomes useful after the create-order request has succeeded and an order object or order ID exists.

Did this answer your question?