Skip to main content

Checkout Process Implementation using Propeller Commerce GraphQL API

In this tutorial, we'll implement a streamlined checkout process for a B2B ordering portal using Propeller Commerce’s GraphQL API. The platform's headless commerce approach simplifies and accelerates the development of highly personalized B2B commerce experiences, particularly for complex, multi-step checkout workflows.

Why It’s Important

A seamless checkout experience is essential for B2B customers. Unlike B2C customers, B2B buyers may handle larger orders, multiple shipping addresses, complex pricing structures and other specific needs. A well-designed checkout process reduces friction and ensures that businesses can quickly and effectively place orders.

Key Topics Covered

  1. Cart Overview
  2. Collecting Customer Details
  3. Applying a Shipping method
  4. Applying a Payment method
  5. Reviewing the Cart
  6. Creating an Unfinished Order
  7. Payment Flow Integration
  8. Order Confirmation

Let’s dive into each of these areas.

1. Cart Overview

Before proceeding to checkout, it's crucial to ensure the cart is still valid, particularly if the user has taken time to review the cart contents or returns after a period of inactivity. This involves checking stock availability, ensuring prices are up to date, and verifying any applied promotions or discounts.

Query Example: Fetching cart

query {
cart(id: "018dcc9a-f965-7434-8fad-369aa9a8c276") {
items {
productId
quantity
price
priceNet
totalPrice
totalPriceNet
}
total {
subTotal
subTotalNet
discountPercentage
totalNet
totalGross
discountNet
discount
}
taxLevels {
taxPercentage
price
}
}
}

Ensure the customer is presented with a clear and detailed summary of their order, including item names, prices, quantities, and the total amount, before allowing them to proceed to the next step of the checkout process.

2. Collecting Customer Details

When implementing the checkout process, it’s essential to collect and update customer details (such as billing and shipping addresses) and ensure the cart reflects these changes in real time. Propeller Commerce’s GraphQL API makes this easy with a series of mutations that allow you to capture and update customer-specific data efficiently.

Retrieve customer details

The first step in collecting customer details is retrieving their stored data, including billing and shipping addresses. This can be done using the viewer query for authenticated users.

Query Example: Fetching customer details

query {
viewer {
__typename
firstName
middleName
lastName
email
gender
phone
mobile
... on Contact {
userId: contactId
company {
companyId
debtorId
name
addresses {
id
code
firstName
middleName
lastName
gender
email
country
city
street
number
numberExtension
postalCode
phone
notes
icp
type
isDefault
}
}
}
... on Customer {
userId: customerId
debtorId
addresses {
id
code
firstName
middleName
lastName
gender
email
country
city
street
number
numberExtension
postalCode
phone
notes
icp
type
isDefault
}
}
}
}

The query includes two inline fragments (... on Contact and ... on Customer) to handle two possible types of users. These fragments ensure that, depending on the user type, the appropriate fields are queried. When the user type is contact, debtorId and addresses are fetched from the company, while in the case of a customer they are fetched from the customer itself.

Query Breakdown:

  • __typename: Returns the GraphQL type (e.g., Contact or Customer) of the object, allowing the client to handle different user types
  • firstName, middleName, lastName: Basic information
  • email: The user's email address
  • gender, phone, mobile: Additional personal details
  • Fields within Contact:
    • userId: contactId: Retrieves the unique identifier for the contact (renaming contactId to userId)
    • company: Company the contact belongs to
      • companyId: Company unique ID
      • debtorId: Company debtor ID
      • name: Company name
      • addresses: Company addresses
  • Fields within Customer:
    • userId: customerId: Retrieves the unique identifier for the customer (renaming customerId to userId)
    • debtorId: Customer debtor ID
    • addresses: Customer addresses
  • Address fields:
    • id, code: The unique ID and optional address code
    • firstName, middleName, lastName, gender: Contact details of the person associated with this address
    • email, phone: Communication details
    • notes: Address notes
    • type: Address type (home, delivery, invoice)
    • icp: Boolean value indicating if no taxes apply for the address
    • isDefault: Indicates if this is the default home/delivery/invoice address for the customer/ company

The collected information should be used to update the cart details, as well as the shipping (delivery) and billing (invoice) addresses. Additionaly, the customer should always be presented with a form where they can modify details.

Update cart addresses

You can update the billing and shipping address using the cartUpdateAddress mutation.

Query Example: Update cart shipping address

mutation {
cartUpdateAddress(
id: "018dcc9a-f965-7434-8fad-369aa9a8c276"
input: {
type: delivery
firstName: "John"
lastName: "Doe"
street: "Elm Street"
number: "123"
city: "New York"
postalCode: "10001"
country: "US"
email: "john.doe@example.com"
}
) {
invoiceAddress {
id
code
firstName
middleName
lastName
gender
email
country
city
street
number
numberExtension
postalCode
phone
notes
}
shippingAddress {
id
code
firstName
middleName
lastName
gender
email
country
city
street
number
numberExtension
postalCode
phone
notes
}
}
}

Query Example: Update cart billing address

mutation {
cartUpdateAddress(
id: "018dcc9a-f965-7434-8fad-369aa9a8c276"
input: {
type: invoice
firstName: "John"
lastName: "Doe"
street: "Elm Street"
number: "123"
city: "New York"
postalCode: "10001"
country: "US"
email: "john.doe@example.com"
}
) {
invoiceAddress {
id
code
firstName
middleName
lastName
gender
email
country
city
street
number
numberExtension
postalCode
phone
notes
}
shippingAddress {
id
code
firstName
middleName
lastName
gender
email
country
city
street
number
numberExtension
postalCode
phone
notes
}
}
}

These mutations update the cart with the customer's billing and shipping details, ensuring they are ready for checkout.

3. Applying a Shipping method

Retrieve shipping methods, carriers, pickup locations

You can retrieve a list of available shipping methods using the following query:

Query Example: Fetching available shipping methods

query {
cart(id: "018dcc9a-f965-7434-8fad-369aa9a8c276") {
shippingMethods {
name
code
}
}
}

Usually, the customer can choose between having their order delivered or picking it up themselves. If they opt for delivery, you'll likely need to present a list of available carriers for them to select from. Alternatively, if they prefer self-pickup, you’ll need to provide a list of pickup locations to choose from. Here's how you can fetch that data:

Query Example: Fetching carriers

query {
cart(id: "018dcc9a-f965-7434-8fad-369aa9a8c276") {
carriers {
id
name
price
deliveryDeadline
}
}
}

Query Breakdown:

  • id: The unique identifier for the carrier
  • name: The name of the shipping carrier (e.g., DHL, FedEx, UPS)
  • price: The cost of using the carrier’s shipping service for the current order. Approx. price. Final price will be calculated when carrier is selected
  • deliveryDeadline: The estimated delivery time or deadline for delivery

The following GraphQL query retrieves a list of active warehouses that serve as pickup locations. It uses specific filters and returns paginated data along with detailed information about each warehouse and its address.

Query Example: Fetching pickup locations

query {
warehouses(input: { isActive: true, isPickupLocation: true }) {
itemsFound
offset
page
pages
start
end
items {
id
name
description
notes
address {
id
firstName
lastName
email
country
city
street
number
numberExtension
region
postalCode
phone
notes
}
businessHours {
dayOfWeek
openingTime
closingTime
}
}
}
}

Query Breakdown:

  • input: Input field of type WarehouseSearchInput
    • isActive: Filters warehouses that are currently active. Boolean value
    • isPickupLocation: Filters warehouses that are designated as pickup locations. Boolean value
  • itemsFound: The total number of warehouse items matching the filters
  • offset: The starting point of the current page of results (useful for pagination)
  • page: The current page number being retrieved
  • pages: The total number of pages available
  • start: The index of the first item in the current page
  • end: The index of the last item in the current page
  • items: Pick up locations found
    • id: The unique identifier for the warehouse
    • name: The warehouse name
    • description: A description of the warehouse, typically providing context or extra information
    • notes: Additional notes regarding the warehouse
    • address: This object holds the address details of the warehouse
      • id: The unique identifier for the address
      • firstName, lastName: Contact information for the person responsible at the warehouse
      • email: Email address for contact purposes
      • country, city, street, number, numberExtension: Detailed address information, including country, street, and number
      • region: The region or state where the warehouse is located
      • postalCode: The postal/ZIP code for the warehouse
      • phone: The contact phone number for the warehouse
      • notes: Additional notes regarding the warehouse address
    • businessHours: This object holds the business hours for the warehouse
      • dayOfWeek: The day of the week (e.g., Monday, Tuesday) for the business hours
      • openingTime: The time the warehouse opens on a specific day
      • closingTime: The time the warehouse closes on a specific day

Update shipping method

Now that you’ve gathered all the necessary data, it’s time to update the cart. Let’s assume the customer has chosen delivery and selected a carrier. Additionally, they can decide whether they want the items delivered in separate shipments if needed. You can update the cart with this information using the cartUpdate mutation.

Mutation Example: Updating cart shipping method

mutation {
cartUpdate(
id: "018dcc9a-f965-7434-8fad-369aa9a8c276"
input: {
postageData: {
method: "shippingmethodcode"
partialDeliveryAllowed: Y
carrier: "carriercode"
}
}
) {
postageData {
method
taxPercentage
price
priceNet
requestDate
carrier
partialDeliveryAllowed
}
total {
subTotal
subTotalNet
discountPercentage
totalNet
totalGross
discountNet
discount
}
taxLevels {
taxPercentage
price
}
}
}

Field Breakdown:

  • input: Input field of type CartUpdateInput
    • postageData: Input field of type CartPostageDataInput
      • method: Shipping method code as retrieved from cart.shippingMethods
      • partialDeliveryAllowed: Indicates whether partial delivery is a preferred option. Possible values are Y and N
      • carrier: Carrier code as retrieved from cart.carriers
  • postageData: Cart postage data (type CartPostageData)
    • method: The shipping method set
    • price: Shipping costs
    • priceNet: Shipping costs, tax included
    • taxPercentage: Shipping costs tax percentage
    • requestDate: Estimated delivery date
    • carrier: Chosen carrier
    • partialDeliveryAllowed: Indicates whether partial delivery is a preferred option. Possible values are Y and N

Shipping costs are automatically calculated by Propeller Commerce's Business Rules, a robust feature that handles various scenarios, including calculating shipping costs based on different conditions. However, if you need to apply custom shipping costs, you can provide the price directly using the CartPostageDataInput.price when updating the cart. Below is an example mutation for setting custom shipping costs.

Mutation Example: Applying custom shipping costs

mutation {
cartUpdate(
id: "018dcc9a-f965-7434-8fad-369aa9a8c276"
input: {
postageData: {
method: "shippingmethodcode"
partialDeliveryAllowed: Y
carrier: "carriercode"
price: 12.50
}
}
) {
postageData {
method
taxPercentage
price
priceNet
requestDate
carrier
partialDeliveryAllowed
priceMode
}
total {
subTotal
subTotalNet
discountPercentage
totalNet
totalGross
discountNet
discount
}
taxLevels {
taxPercentage
price
}
}
}

The response includes an additional field: postageData.priceMode, which indicates whether the shipping costs are determined by Propeller Commerce Business Rules (PLATFORM) or are custom-set (EXTERNAL).

If the customer chooses to pick up their order and selects a pickup location, you can update the cart with this information using the cartUpdate mutation.

Mutation Example: Updating cart shipping method

mutation {
cartUpdate(
id: "018dcc9a-f965-7434-8fad-369aa9a8c276"
input: { postageData: { method: "pickupmethodcode", pickUpLocationId: 1 } }
) {
postageData {
method
taxPercentage
price
priceNet
requestDate
pickUpLocationId
}
total {
subTotal
subTotalNet
discountPercentage
totalNet
totalGross
discountNet
discount
}
taxLevels {
taxPercentage
price
}
}
}

The field input.postageData.pickUpLocationId specifies the pickup location. You’ll need to provide a valid ID, which can be obtained from the warehouses query: warehouses.items.id.

4. Applying a Payment method

Payment method selection is the final step in checkout preparation. You’ll need to ensure that customers can choose from their preferred payment options (e.g., credit card, PayPal, bank transfer, on account, etc.).

Retrieve payment methods

You can retrieve a list of available payment methods using the following query:

Query Example: Fetching available payment methods

query {
cart(id: "018dcc9a-f965-7434-8fad-369aa9a8c276") {
payMethods {
name
code
taxCode
price
type
}
}
}

Query Breakdown:

  • code: The unique identifier for the payment method
  • type: The type of the payment method
  • name: The name of the payment method
  • price: The transaction cost for using the selected payment method. Final price will be calculated when payment method is set via the cartUpdate mutation
  • taxCode: Tax code applicable to payment method costs

Now that you’ve gathered all the necessary data, it’s time to update the cart using the cartUpdate mutation.

Mutation Example: Updating cart payment method

mutation {
cartUpdate(
id: "018dcc9a-f965-7434-8fad-369aa9a8c276"
input: { paymentData: { method: "paymentmethodcode", status: "OPEN" } }
) {
paymentData {
method
taxPercentage
price
priceNet
status
statusDate
}
total {
subTotal
subTotalNet
discountPercentage
totalNet
totalGross
discountNet
discount
}
taxLevels {
taxPercentage
price
}
}
}

Field Breakdown:

  • input: Input field of type CartUpdateInput
    • paymentData: Input field of type CartPaymentDataInput
      • method: Payment method code as retrieved from cart.payMethods
      • status: Payment status
  • paymentData: Cart payment data (type CartPaymentData)
    • method: The payment method set
    • price: Transaction costs
    • priceNet: Transaction costs, tax included
    • taxPercentage: Transaction costs price percentage
    • status: Payment status
    • statusDate: Payment status date modified

Transaction costs are automatically calculated by Propeller Commerce's Business Rules. However, if you need to apply custom transaction costs, you can provide the price directly using the CartPaymentDataInput.price when updating the cart. Below is an example mutation for setting custom transaction costs.

Mutation Example: Applying custom transaction costs

mutation {
cartUpdate(
id: "018dcc9a-f965-7434-8fad-369aa9a8c276"
input: {
paymentData: { method: "paymentmethodcode", status: "OPEN", price: 2.50 }
}
) {
paymentData {
method
taxPercentage
price
priceNet
status
statusDate
priceMode
}
total {
subTotal
subTotalNet
discountPercentage
totalNet
totalGross
discountNet
discount
}
taxLevels {
taxPercentage
price
}
}
}

The response includes an additional field: paymentData.priceMode, which indicates whether the transaction costs are determined by Propeller Commerce Business Rules (PLATFORM) or are custom-set (EXTERNAL).

5. Reviewing the Cart

Once all customer, shipping, and payment details have been applied, allow the customer to review their order. This step gives them a chance to confirm all the information before submitting the final order.

Query Example: Fetching final cart data for review

query {
cart(id: "018dcc9a-f965-7434-8fad-369aa9a8c276") {
items {
productId
quantity
price
priceNet
totalPrice
totalPriceNet
}
postageData {
method
taxPercentage
price
priceNet
}
paymentData {
method
taxPercentage
price
priceNet
}
total {
subTotal
subTotalNet
discountPercentage
totalNet
totalGross
discountNet
discount
}
taxLevels {
taxPercentage
price
}
}
}

6. Creating an Unfinished Order

The final step is to create an order from the cart. If the customer has chosen to pay immediately, you'll need to integrate the payment method flow. Before proceeding with the payment, the cart must first be converted into an order, which can be done using the cartProcess mutation. If the customer has chosen to pay on account, this step can be skipped.

Mutation Example: Convert cart to order

mutation {
cartProcess(
id: "018dcc9a-f965-7434-8fad-369aa9a8c276"
input: { orderStatus: "UNFINISHED" }
) {
cartOrderId
}
}

The response includes the cartOrderId which is the ID of the order just created. You will need to use this as a reference when integrating a payment flow.

7. Payment Flow Integration

While integrating a payment flow is essential for completing transactions, it's important to note that this process is not covered by Propeller Commerce's functionality. Propeller Commerce can be used to store and track payment transaction history, but the actual payment processing must be handled by an external payment gateway.

Best practices for payment flow integration

  1. Use secure payment gateways: Always integrate with trusted and secure payment gateways (e.g., Stripe, PayPal, or other relevant providers) to handle payment transactions.
  2. PCI compliance: Ensure that your integration meets PCI DSS (Payment Card Industry Data Security Standard) requirements to protect customer payment information.
  3. Redirect to payment provider: For additional security, consider redirecting customers to the payment provider's page to complete transactions, reducing the need to store sensitive payment details on your system.
  4. Handle payment states: Be sure to account for different payment states (e.g., success, failure, pending) and update the order status accordingly, offering clear feedback to the customer.
  5. Confirm order upon payment completion: Only confirm the order after successful payment authorization, ensuring the customer receives immediate confirmation of their order.

By following these best practices, you can create a secure and reliable payment flow that complements the checkout process.

Track payment in Propeller Commerce

You can keep track of payment history in Propeller Commerce using the paymentCreate, paymentUpdate, paymentDelete mutations.

Mutation Example: Create payment

mutation {
paymentCreate(
input: {
orderId: 534
amount: 128.50
currency: "EUR"
method: "MOLLIE"
status: OPEN
addTransaction: {
transactionId: "transactionIDfromPSP"
paymentId: "paymentIdfromPSP"
amount: 128.50
currency: "EUR"
timestamp: "2019-12-03T09:54:33Z"
type: PAY
provider: "MOLLIE"
status: OPEN
}
}
) {
id
}
}

Field breakdown:

  • input: Input field of type CreatePaymentInput
    • orderId: The unique identifier of the order for which the payment is being created. This links the payment to the respective order
    • amount: The total amount of the payment (in this case, €128.50)
    • currency: The currency of the payment (e.g., "EUR" for euros)
    • method: The payment method being used. In this example, the method is "MOLLIE", which refers to the payment service provider
    • status: The status of the payment. Possible statuses include OPEN, PENDING, PAID, etc. In this example, the status is OPEN, indicating the payment process is not yet finalized. For more details check Propeller Commerce API documentation.
    • addTransaction: This field contains transaction-specific details for the payment. This information typically comes from the payment service provider (PSP)
      • transactionId: The unique identifier for the transaction, as provided by the payment service provider
      • paymentId: The unique identifier for the payment itself, as assigned by the payment service provider
      • amount: The amount involved in the transaction, which matches the main payment amount
      • currency: The currency for the transaction, in this case, "EUR"
      • timestamp: The timestamp for when the transaction was processed, represented in ISO 8601 format (e.g., "2019-12-03T09:54:33Z")
      • type: The type of transaction, such as PAY (payment), REFUND, etc. In this case, the type is PAY, indicating it's a payment transaction. For more details check Propeller Commerce API documentation
      • provider: The payment provider handling the transaction. Here, it is "MOLLIE"
      • status: The status of the transaction, which is also set to OPEN, indicating the transaction is still in progress. For more details, check Propeller Commerce API documentation

You can update a payment using paymentUpdate mutation.

Mutation Example: Update payment

mutation {
paymentUpdate(
searchBy: { orderId: 534 }
input: {
status: PAID
addTransaction: {
transactionId: "transactionIDfromPSP"
paymentId: "paymentIdfromPSP"
amount: 128.50
currency: "EUR"
timestamp: "2019-12-03T09:55:33Z"
type: PAY
provider: "MOLLIE"
status: SUCCESS
}
}
) {
id
}
}

This mutation updates the payment status and adds another transaction with status SUCCESS to the previously created payment. The payment can be identified not only by the order ID, but also by the Propeller payment ID or PSP payment ID. For more details check Propeller Commerce API documentation.

If needed, a payment can be deleted using the paymentDelete mutation. For more details check Propeller Commerce API documentation.

Handling unsuccessful payments

If the payment failed, you'll need to update the order status and payment status. You can do this using the orderSetStatus mutation.

Mutation Example: Update order status

mutation {
orderSetStatus(
input: { orderId: 534, status: "UNSUCCESSFUL", payStatus: "FAILED" }
) {
id
status
paymentData {
status
statusDate
}
}
}

Incorporating a payment flow is a crucial step in completing transactions, though it's important to recognize that Propeller Commerce does not handle the actual payment processing. Instead, Propeller Commerce serves as a platform for tracking and storing payment transaction history. By integrating secure and trusted payment gateways, following PCI compliance, and managing payment states, you can ensure a reliable and secure transaction process for your customers.

In Propeller Commerce, you can manage payments using mutations like paymentCreate, paymentUpdate, and paymentDelete, allowing you to track, update, or remove payment records as necessary. These actions offer flexibility in handling payment statuses and transactions while maintaining a clear record within your commerce system. By combining external payment solutions with Propeller’s ability to manage transaction histories, you ensure that both payment handling and tracking are effectively managed.

Lastly, always be sure to handle unsuccessful payments by updating the corresponding order and payment statuses to keep the customer experience smooth and transparent.

8. Order Confirmation

Congratulations! You've successfully placed an order using Propeller Commerce and handled the payment. To complete the process, you can update the order status and send a confirmation email to the customer. This can be done using the orderSetStatus mutation.

Mutation Example: Update order status

mutation {
orderSetStatus(
input: {
orderId: 534
status: "PAID"
payStatus: "NEW"
sendOrderConfirmationEmail: true
addPDFAttachment: true
deleteCart: true
}
) {
id
status
paymentData {
status
statusDate
}
}
}
  • input: Input field of type OrderSetStatusInput
    • orderId: The unique identifier of the order being updated. In this example, the order ID is 534
    • status: The new status for the order. In this case, the status is set to "NEW", indicating a newly registered payment status
    • payStatus: The payment status, which can represent the current state of the payment. Here, it's set to "PAID", indicating that the order has been fully paid
    • sendOrderConfirmationEmail: A boolean flag (true/false) indicating whether an order confirmation email should be sent to the customer. In this example, it's set to true
    • addPDFAttachment: A boolean flag (true/false) that, when set to true, attaches a PDF of the order (such as an invoice) to the confirmation email
    • deleteCart: A boolean flag (true/false) specifying whether the cart associated with the order should be deleted after the order is placed. In this case, it is set to true

This mutation is essential for finalizing an order in a Propeller Commerce-powered system and managing customer communication and order status updates.

Conclusion

In this tutorial, we’ve walked through the full checkout process using Propeller Commerce’s GraphQL API, covering every essential step from cart validation to order confirmation. By following this streamlined process, you can provide customers with a seamless and efficient checkout experience that handles complex needs such as multiple shipping addresses, custom pricing, various payment methods and order confirmation.