Quotes

Plan: B2B Developer

Lesson 10 of 18 · 30 min

Introduction

Quotes are a key part of the B2B buyer experience, allowing users to negotiate prices for a potential order with a sales representative.

The B2B GraphQL API fully supports the user side of the quoting experience, with operations for submitting quotes, viewing up-to-date quote details, sending messages to sales reps, and initiating checkout from a custom quote.

Fetching Currency Details

You should supply explicit currency information when submitting a quote. Since the details needed are more extensive than just the currency code - including the proper currency token, separator characters, and exchange rate as configured in your BigCommerce store settings - the most reliable way to obtain these details is to fetch them with an appropriate query.

The currencies query will return information about the available currencies for the specified channel. The details are determined by your currency configuration in your BigCommerce store control panel.

Example query and response:

query GetCurrencies(
$storeHash: String!,
$channelId: String!
) {
currencies(
storeHash: $storeHash,
channelId: $channelId,
) {
currencies {
currency_code,
currency_exchange_rate,
token,
decimal_token,
decimal_places,
token_location,
thousands_token,
},
channelCurrencies
}
}

Obtaining Product Details

Submitting a request for quote requires the following details for each product included in the quote:

  • Product ID
  • The specific variant ID (this includes the base variant for simple products)
  • SKU
  • Product name
  • The product’s list price
  • An image URL to display when the quote is viewed

Your application will typically start with the known IDs of the products you need to query for a quote request. For example, a user might be initiating an “add to quote” action from a product listing page by clicking on a specific product. Starting with IDs, you can use the productsSearch query to obtain the rest of the product information necessary for a quote request, as well as several other product details.

Example query, variables, and response:

query SearchProducts(
$productIds: [Int],
$companyId: String
) {
productsSearch(
productIds: $productIds,
companyId: $companyId
) {
id
name
sku
availability
variants
imageUrl
inventoryLevel
inventoryTracking
availability
optionsV3
productUrl
}
}

In the case of products with variant options, the option_values of each variant in the result set can be compared with the option details selected by the user in your application’s UI in order to find the appropriate variant for the quote request.

With a result set like the above, the following values would be appropriate for the minimum product details needed for a quote request:

  • Product ID: The product ID(s) you started with, or else the id of the matching product in the result set
  • Variant ID: The variant_id of the matching variant
  • SKU: The sku of the matching variant
  • Product Name: The name of the matching product
  • List Price: The calculated price_of the matching variant
  • Image URL: The image_url of the matching variant

The productsSearch query can also be performed with a search term rather than product IDs.

Example Request:

query SearchProducts(
$search: String,
$companyId: String
) {
productsSearch(
search: $search,
companyId: $companyId
) {
...
}
}

The versatility of productsSearch can make it useful for a variety of use cases in your front-end application beyond quoting.

Submitting a Quote Request

With the appropriate details fetched for the products you wish to request a quote for, the request can be submitted with the quoteCreate mutation.

The quoteCreate mutation is available to any of the built-in user roles, or a custom role with the “Quotes - Request for quote” permission.

Note that the involvement of the GraphQL API and the quoteCreate mutation should occur at the point in your application when the user is actually ready to submit a request for quote. As the user is “building” the details of the quote, your application should handle tracking the quote details in its own storage.

As an example, the built-in Buyer Portal utilizes browser local storage, via the Redux library, to store quote details until the user is ready to submit.

Example mutation, variables, and response:

mutation CreateQuote(
$message: String,
$subtotal: Decimal!,
$discount: Decimal!,
$grandTotal: Decimal!,
$userEmail: String,
$quoteTitle: String,
$shippingAddress: ShippingAddressInputType!,
$billingAddress: BillingAddressInputType!,
$contactInfo: ContactInfoInputType!,
$companyId: Int,
$currency: CurrencyInputType!,
$storeHash: String!,
$productList: [ProductInputType]!
) {
quoteCreate(
quoteData: {
message: $message,
subtotal: $subtotal,
discount: $discount,
grandTotal: $grandTotal,
userEmail: $userEmail,
quoteTitle: $quoteTitle,
shippingAddress: $shippingAddress,
billingAddress: $billingAddress,
contactInfo: $contactInfo,
companyId: $companyId,
currency: $currency,
storeHash: $storeHash,
productList: $productList
}
) {
quote {
id
createdAt
quoteNumber
quoteTitle
quoteUrl
}
}
}

The way that various price totals in the quote data relate and are validated is a key detail to understand about the quote submission process:

  • The discount field on the quote and on each item in productList must be provided but must be 0. This field will be updated to reflect any discount offered by the sales rep responding to the quote request.
  • The offeredPrice on each item in productList must be provided but must be equal to basePrice. This field will be updated to reflect the discounted prices offered by the sales rep.
  • The subtotal and grandTotal fields on the quote should be equal and must equal the sum of each product’s offeredPrice multiplied by its quantity. grandTotal represents the current quote total including discounts, and therefore it will be updated to reflect the sales rep’s changes.

Other notable details about the quoteCreate mutation:

  • Any value provided in the message field will create the initial entry in the quote’s message thread between the sales rep and buyer.
  • userEmail must match the email address of one of the company’s users.
  • The details of contactInfo are static information attached to the quote and can include any values.
  • As seen in the example above, complex products can be added to the quote by providing a variantId. However, it’s also possible to provide the details of the configured options/values with the options field instead.

Fetching Quote Details

The quotes query can be used to fetch the company’s quote details.

The quotesand quote queries are available to any of the built-in user roles, or a custom role with the “Quotes - View” permission.

Quote details will reflect all changes made by a sales rep in response to the original request, including new messages, so that storefront users can review the updated quote and respond or proceed to checkout.

Example query and response:

query GetQuotes {
quotes {
pageInfo {
hasNextPage
hasPreviousPage
startCursor
endCursor
}
totalCount
edges {
node {
id
createdAt
quoteNumber
quoteTitle
createdBy
createdByEmail
expiredAt
subtotal
discount
discountType
discountValue
shippingTotal
taxTotal
grandTotal
totalAmount
contactInfo
trackingHistory
notes
legalTerms
shippingMethod
billingAddress
shippingAddress
salesRepInfo {
salesRepName
salesRepEmail
}
productsList {
productId
variantId
sku
basePrice
discount
offeredPrice
quantity
imageUrl
productName
options
notes
productUrl
type
}
quoteUrl
}
}
}
}
  • The quotes query supports standard pagination arguments.
  • Other filter arguments include fields like quoteNumber, statusand quoteTitle.
  • Quotes can also be filtered by date range using a combination of the arguments dateCreatedBeginAt, dateCreatedEndAt, dateUpdatedBeginAt, dateUpdatedEndAt, dateExpiredBeginAt, and dateExpiredEndAt. These arguments receive a string date value like “2025-01-01”.
  • createdAt and expiredAt are expressed as timestamps. expiredAt represents the quote’s expiration date, after which the quote cannot be converted into an order.
  • At the line item level, discount will be expressed as a percentage and offeredPrice will reflect this discount if a per-line-item or total percentage discount was applied by the sales rep. If a fixed amount discount was applied to the quote, discount will be 0 and offeredPrice will still reflect the original price.
  • At the quote level, discount will always express the total fixed amount that was discounted across the entire quote. If a per-line-item discount was applied, discountType will be 0 and discountValue will be empty. If a total percentage discount was applied, discountType will be 1 and discountValue will be expressed as a percentage. And if a fixed amount discount was applied, discountType will be 2 and discountValue will equal the fixed discount amount.
  • subtotal will reflect the original price of all items in the quote, grandTotal will reflect the applied discount, and totalAmount will also factor in any applied shipping and taxes.
  • trackingHistory contains the quote’s message history, including messages from both the buyer and sales rep.
  • notes contains any overall notes the sales rep has attached to the quote, while legalTerms contains any noted terms and conditions.

The quote query can be used to fetch the details of a specific quote. It returns the same GraphQL type as each node returned by quotes, so the same fields can be queried. This query requires not only the ID of the quote, but also the store hash and the correct createdAt timestamp.

Example Query:

query GetQuote(
$id: Int!,
$storeHash: String!,
$date: String!
) {
quote(
id: $id,
storeHash: $storeHash,
date: $date
) {
...
}
}

Quote PDFs

In addition to querying quote data directly, the GraphQL API can also be used to fetch a URL to download the quote in PDF format, using the quoteFrontendPdf mutation.

The quoteFrontendPdf mutation is available to any of the built-in user roles, or a custom role with the “Quotes - View” permission.

This mutation requires the same arguments as the quote query, as well as a lang argument.

Example mutation and response:

mutation ExportQuotePdf(
$createdAt: Int!,
$quoteId: Int!,
$storeHash: String!
) {
quoteFrontendPdf(
createdAt: $createdAt,
lang: "en",
quoteId: $quoteId,
storeHash: $storeHash
) {
url
}
}

Note that the effect of the lang argument depends on whether a quote PDF has previously been sent by a sales rep. If the sales rep has emailed a PDF, the language value will already be set and will override any value passed in the argument. Otherwise, static text in the PDF will reflect the lang that is passed.

Sending Quote Messages

For the most part, updates to a quote’s data will be done by a sales rep in the B2B admin, rather than by the storefront API. However, the GraphQL API does make it possible to send further quote messages from the perspective of the buyer, using the quoteUpdate mutation.

The quoteUpdate mutation is available to any of the built-in user roles, or a custom role with the “Quotes - Request for quote” permission.

Example mutation and variables:

mutation AddQuoteMessage(
$id: Int!,
$storeHash: String!,
$userEmail: String!,
$message: String
) {
quoteUpdate(
id: $id,
quoteData: {
storeHash: $storeHash,
userEmail: $userEmail,
message: $message
}
) {
quote {
createdAt
quoteNumber
quoteTitle
quoteUrl
}
}
}

Converting a Quote to an Order

Once the details of a quote have been settled, the final step from a storefront perspective is to convert the quote into an order by proceeding to checkout.

The quoteCheckout mutation loads the custom quote details into a BigCommerce cart and returns the appropriate URLs to allow a storefront user to complete the checkout process.

The quoteCheckout mutation is available only when the user associated with the Bearer token has the Senior Buyer role or higher, or a custom role with the “Quotes - Convert to order” permission.

The mutation requires the quote ID and the store hash as arguments.

Example mutation and response:

mutation GetQuoteCheckout(
$id: Int!,
$storeHash: String!
) {
quoteCheckout(
id: $id,
storeHash: $storeHash
) {
quoteCheckout {
checkoutUrl
cartId
cartUrl
}
}
}

Redirecting a user to the cartUrl will load a cart based on the quote details into the user’s session and direct them to the storefront’s cart page, while the checkoutUrl will direct them to the checkout page.

If your storefront uses a custom-built checkout experience rather than the default BigCommerce checkout, it is possible to utilize the BigCommerce REST Management API to manually construct a cart with custom pricing based on a valid quote, but this is not a workflow we will cover here.