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
- Cart Overview
- Collecting Customer Details
- Applying a Shipping method
- Applying a Payment method
- Reviewing the Cart
- Creating an Unfinished Order
- Payment Flow Integration
- 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
orCustomer
) of the object, allowing the client to handle different user typesfirstName
,middleName
,lastName
: Basic informationemail
: The user's email addressgender
,phone
,mobile
: Additional personal details- Fields within
Contact
:userId: contactId
: Retrieves the unique identifier for the contact (renamingcontactId
touserId
)company
: Company the contact belongs tocompanyId
: Company unique IDdebtorId
: Company debtor IDname
: Company nameaddresses
: Company addresses
- Fields within
Customer
:userId: customerId
: Retrieves the unique identifier for the customer (renamingcustomerId
touserId
)debtorId
: Customer debtor IDaddresses
: Customer addresses
- Address fields:
id
,code
: The unique ID and optional address codefirstName
,middleName
,lastName
,gender
: Contact details of the person associated with this addressemail
,phone
: Communication detailsnotes
: Address notestype
: Address type (home
,delivery
,invoice
)icp
: Boolean value indicating if no taxes apply for the addressisDefault
: 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 carriername
: 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 selecteddeliveryDeadline
: 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 typeWarehouseSearchInput
isActive
: Filters warehouses that are currently active. Boolean valueisPickupLocation
: Filters warehouses that are designated as pickup locations. Boolean value
itemsFound
: The total number of warehouse items matching the filtersoffset
: The starting point of the current page of results (useful for pagination)page
: The current page number being retrievedpages
: The total number of pages availablestart
: The index of the first item in the current pageend
: The index of the last item in the current pageitems
: Pick up locations foundid
: The unique identifier for the warehousename
: The warehouse namedescription
: A description of the warehouse, typically providing context or extra informationnotes
: Additional notes regarding the warehouseaddress
: This object holds the address details of the warehouseid
: The unique identifier for the addressfirstName
,lastName
: Contact information for the person responsible at the warehouseemail
: Email address for contact purposescountry
,city
,street
,number
,numberExtension
: Detailed address information, including country, street, and numberregion
: The region or state where the warehouse is locatedpostalCode
: The postal/ZIP code for the warehousephone
: The contact phone number for the warehousenotes
: Additional notes regarding the warehouse address
businessHours
: This object holds the business hours for the warehousedayOfWeek
: The day of the week (e.g., Monday, Tuesday) for the business hoursopeningTime
: The time the warehouse opens on a specific dayclosingTime
: 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 typeCartUpdateInput
postageData
: Input field of typeCartPostageDataInput
method
: Shipping method code as retrieved fromcart.shippingMethods
partialDeliveryAllowed
: Indicates whether partial delivery is a preferred option. Possible values areY
andN
carrier
: Carrier code as retrieved fromcart.carriers
postageData
: Cart postage data (typeCartPostageData
)method
: The shipping method setprice
: Shipping costspriceNet
: Shipping costs, tax includedtaxPercentage
: Shipping costs tax percentagerequestDate
: Estimated delivery datecarrier
: Chosen carrierpartialDeliveryAllowed
: Indicates whether partial delivery is a preferred option. Possible values areY
andN
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 methodtype
: The type of the payment methodname
: The name of the payment methodprice
: The transaction cost for using the selected payment method. Final price will be calculated when payment method is set via thecartUpdate
mutationtaxCode
: 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 typeCartUpdateInput
paymentData
: Input field of typeCartPaymentDataInput
method
: Payment method code as retrieved fromcart.payMethods
status
: Payment status
paymentData
: Cart payment data (typeCartPaymentData
)method
: The payment method setprice
: Transaction costspriceNet
: Transaction costs, tax includedtaxPercentage
: Transaction costs price percentagestatus
: Payment statusstatusDate
: 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
- Use secure payment gateways: Always integrate with trusted and secure payment gateways (e.g., Stripe, PayPal, or other relevant providers) to handle payment transactions.
- PCI compliance: Ensure that your integration meets PCI DSS (Payment Card Industry Data Security Standard) requirements to protect customer payment information.
- 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.
- 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.
- 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 orderamount
: 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 providerstatus
: The status of the payment. Possible statuses includeOPEN
,PENDING
,PAID
, etc. In this example, the status isOPEN
, 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 providerpaymentId
: The unique identifier for the payment itself, as assigned by the payment service provideramount
: The amount involved in the transaction, which matches the main payment amountcurrency
: 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 asPAY
(payment),REFUND
, etc. In this case, the type isPAY
, indicating it's a payment transaction. For more details check Propeller Commerce API documentationprovider
: The payment provider handling the transaction. Here, it is "MOLLIE"status
: The status of the transaction, which is also set toOPEN
, 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 typeOrderSetStatusInput
orderId
: The unique identifier of the order being updated. In this example, the order ID is534
status
: The new status for the order. In this case, the status is set to "NEW", indicating a newly registered payment statuspayStatus
: 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 paidsendOrderConfirmationEmail
: A boolean flag (true
/false
) indicating whether an order confirmation email should be sent to the customer. In this example, it's set totrue
addPDFAttachment
: A boolean flag (true
/false
) that, when set totrue
, attaches a PDF of the order (such as an invoice) to the confirmation emaildeleteCart
: 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 totrue
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.