Creating a Storefront App
This guide covers a series of queries and mutations to handle typical storefront application scenarios.
Let's set up our Firmhouse project before we begin. You can find step-by-step instructions here to configure your project. Below is a list of things you should complete before we start:
Create products. You can follow this guide.
If your project type is Product-as-a-service, Create at least one Plan to use as your default plan. You can follow this guide.
Set up a payment provider.
Generate a Project Access Token by going to the Settings > Integrations page. For this tutorial a token with
Storefront
access type will be sufficient.
Fetching products
To display the products, you need to use the products
query. This query gives you all the information about the products you have set up in the Firmhouse Portal.
const projectAccessToken = '<Your-project-access-token>'
const headers = new Headers({
'Content-Type': 'application/json',
'X-Project-Access-Token': projectAccessToken
})
const graphql = JSON.stringify({
query: `query products($after: String, $first: Int){
products(after: $after, first: $first) {
totalCount
pageInfo {
endCursor
hasNextPage
hasPreviousPage
startCursor
}
nodes {
available
eligibleForDiscount
graceCancellationEnabled
graceCancellationPeriod
graceCancellationUnit
id
imageUrl
interval
intervalUnitOfMeasure
mandatory
maximumCommitmentEnabled
maximumCommitmentPeriod
maximumCommitmentUnit
metadata
minimumCommitmentEnabled
minimumCommitmentPeriod
minimumCommitmentUnit
nthProductFree
priceCents
priceExcludingTaxCents
priceWithSymbol
productType
shopifyProductId
shopifyVariantId
sku
slug
supplier
taxAmountCents
taxPercentage
title
}
}
}
`,
variables: {
after: null,
first: 10
}
})
const requestOptions = {
method: 'POST',
headers,
body: graphql,
};
const response = await fetch("https://portal.firmhouse.com/graphql", requestOptions)
const data = await response.json()
const {totalCount, nodes: products, pageInfo} = data.data.products
Fetching Plans
If your project's subscription type is Product as Service, you can use the plans query. This query gives you all the information about the plans you have set up in the Firmhouse Portal.
const projectAccessToken = '<Your-project-access-token>'
const headers = new Headers({
'Content-Type': 'application/json',
'X-Project-Access-Token': projectAccessToken
})
const graphql = JSON.stringify({
query: `query plans($after: String, $first: Int){
plans(after: $after, first: $first) {
totalCount
nodes {
available
currency
graceCancellationEnabled
graceCancellationPeriod
graceCancellationUnit
id
imageUrl
initialAmountExcludingTaxCents
initialAmountIncludingTaxCents
instalmentIntervalPeriod
instalmentIntervalUnit
instalments
maximumCommitmentEnabled
maximumCommitmentPeriod
maximumCommitmentUnit
metadata
minimumCommitmentEnabled
minimumCommitmentPeriod
minimumCommitmentUnit
monthlyAmountCents
monthlyAmountExcludingTaxCents
monthlyAmountIncludingTaxCents
name
slug
taxAmountCents
taxPercentage
planProducts {
quantity
product {
id
}
}
}
pageInfo {
endCursor
hasNextPage
hasPreviousPage
startCursor
}
}
}
`,
variables: {
after: null,
first: 10
}
})
const requestOptions = {
method: 'POST',
headers,
body: graphql,
};
const response = await fetch("https://portal.firmhouse.com/graphql", requestOptions)
const data = await response.json()
const {totalCount, nodes: plans, pageInfo} = data.data.plans
Creating a cart
To start ordering products, you need to create a cart with createCart
mutation.
const headers = new Headers({
'Content-Type': 'application/json',
'X-Project-Access-Token': projectAccessToken
})
const graphql = JSON.stringify({
query: `mutation {
createCart(input: {}){
subscription {
token
}
}
}
`
})
const requestOptions = {
method: 'POST',
headers,
body: graphql,
};
const response = await fetch("https://portal.firmhouse.com/graphql", requestOptions)
const body = await response.json()
const subscriptionToken = body.data.createCart.subscription.token
Adding products to the cart
Once the cart is created, you can start adding items to it with createOrderedProduct
mutation. Note that we're using the subscriptionToken
value in X-Subscription-Token
header.
const headers = new Headers({
'Content-Type': 'application/json',
'X-Project-Access-Token': token,
'X-Subscription-Token': subscriptionToken
})
const graphql = JSON.stringify({
query: `mutation CreateOrderedProduct($input: CreateOrderedProductInput!) {
createOrderedProduct(input: $input) {
errors
orderedProduct {
createdAt
graceCancellationEndsAt
id
interval
intervalUnitOfMeasure
maximumCommitmentEndsAt
metadata
minimumCommitmentEndsAt
priceExcludingTaxCents
priceIncludingTaxCents
productId
quantity
recurring
shipmentDate
status
title
totalAmountExcludingTaxCents
totalAmountIncludingTaxCents
totalOrdered
updatedAt
}
subscription {
amountForStartingSubscriptionCents
currency
metadata
monthlyAmount
monthlyAmountCents
orderedProducts {
createdAt
graceCancellationEndsAt
id
interval
intervalUnitOfMeasure
maximumCommitmentEndsAt
metadata
minimumCommitmentEndsAt
priceExcludingTaxCents
priceIncludingTaxCents
productId
quantity
recurring
status
title
totalAmountExcludingTaxCents
totalAmountIncludingTaxCents
totalOrdered
updatedAt
}
}
}
}
`,
variables: {
input: {
productId: products[0].id, // You can use any product id here
quantity: 1,
// metadatata: {key: "value"},
// ensureNewRecord: true,
}
}
})
const requestOptions = {
method: 'POST',
headers,
body: graphql,
};
const response = await fetch("https://portal.firmhouse.com/graphql", requestOptions)
const body = await response.json()
const { orderedProduct, subscription, errors } = body.data.createOrderedProduct
You can use the orderedProduct
variable to display notifications about added products. To show cart information, such as all ordered products, total price, payment terms, etc., you can use the subscription
variable.
Removing products from the cart
To let users remove products from their cart, you can use the destroyOrderedProduct
mutation.
const headers = new Headers({
'Content-Type': 'application/json',
'X-Project-Access-Token': token,
'X-Subscription-Token': subscriptionToken
})
const graphql = JSON.stringify({
query: `mutation DestroyOrderedProduct($input: DestroyOrderedProductInput!) {
destroyOrderedProduct(input: $input) {
orderedProduct {
createdAt
graceCancellationEndsAt
id
interval
intervalUnitOfMeasure
maximumCommitmentEndsAt
metadata
minimumCommitmentEndsAt
priceExcludingTaxCents
priceIncludingTaxCents
productId
quantity
recurring
shipmentDate
status
title
totalAmountExcludingTaxCents
totalAmountIncludingTaxCents
totalOrdered
updatedAt
}
subscription {
amountForStartingSubscriptionCents
currency
metadata
monthlyAmount
monthlyAmountCents
orderedProducts {
createdAt
graceCancellationEndsAt
id
interval
intervalUnitOfMeasure
maximumCommitmentEndsAt
metadata
minimumCommitmentEndsAt
priceExcludingTaxCents
priceIncludingTaxCents
productId
quantity
recurring
status
title
totalAmountExcludingTaxCents
totalAmountIncludingTaxCents
totalOrdered
updatedAt
}
}
}
}
`,
variables: {
input: {
id: orderedProduct.id, // You can use any ordered product id here
}
}
})
const requestOptions = {
method: 'POST',
headers,
body: graphql,
};
const response = await fetch("https://portal.firmhouse.com/graphql", requestOptions)
const { errors, data } = await response.json()
const { orderedProduct: removedProduct, subscription } = data?.destroyOrderedProduct ?? {}
The fields retrieved in this query are the same as those in createOrderedProduct
. Therefore you can use the same logic to handle the response that updates the Cart UI or notifications.
Updating the quantities of products in the cart
To allow users to change the quantities of products in their shopping cart, you can use the updateOrderedProductQuantity
mutation.
const headers = new Headers({
'Content-Type': 'application/json',
'X-Project-Access-Token': token,
'X-Subscription-Token': subscriptionToken
})
const graphql = JSON.stringify({
query: `mutation UpdateOrderedProductQuantity($input: UpdateOrderedProductQuantityInput!) {
updateOrderedProductQuantity(input: $input) {
orderedProduct {
createdAt
graceCancellationEndsAt
id
interval
intervalUnitOfMeasure
maximumCommitmentEndsAt
metadata
minimumCommitmentEndsAt
priceExcludingTaxCents
priceIncludingTaxCents
productId
quantity
recurring
shipmentDate
status
title
totalAmountExcludingTaxCents
totalAmountIncludingTaxCents
totalOrdered
updatedAt
}
subscription {
amountForStartingSubscriptionCents
currency
metadata
monthlyAmount
monthlyAmountCents
orderedProducts {
createdAt
graceCancellationEndsAt
id
interval
intervalUnitOfMeasure
maximumCommitmentEndsAt
metadata
minimumCommitmentEndsAt
priceExcludingTaxCents
priceIncludingTaxCents
productId
quantity
recurring
status
title
totalAmountExcludingTaxCents
totalAmountIncludingTaxCents
totalOrdered
updatedAt
}
}
}
}
`,
variables: {
input: {
orderedProduct: {
id: orderedProduct.id, // You can use any ordered product id here
quantity: 2
}
}
}
})
const requestOptions = {
method: 'POST',
headers,
body: graphql,
};
const response = await fetch("https://portal.firmhouse.com/graphql", requestOptions)
const body = await response.json()
const { orderedProduct: updatedProduct, subscription, errors } = body.data.updateOrderedProductQuantity
The fields retrieved in this query are the same as those in createOrderedProduct
. Therefore you can use the same logic to handle the response that updates the Cart UI or notifications.
Updating the subscription plan
You can change the active subscription plan with updatePlan
mutation.
const headers = new Headers({
'Content-Type': 'application/json',
'X-Project-Access-Token': token,
'X-Subscription-Token': subscriptionToken
})
const graphql = JSON.stringify({
query: `mutation UpdatePlan($planSlug: String!) {
updatePlan(input: { planSlug: $planSlug }) {
subscription {
activePlan {
available
currency
graceCancellationEnabled
graceCancellationPeriod
graceCancellationUnit
id
imageUrl
initialAmountExcludingTaxCents
initialAmountIncludingTaxCents
instalmentIntervalPeriod
instalmentIntervalUnit
instalments
maximumCommitmentEnabled
maximumCommitmentPeriod
maximumCommitmentUnit
metadata
minimumCommitmentEnabled
minimumCommitmentPeriod
minimumCommitmentUnit
monthlyAmountCents
monthlyAmountExcludingTaxCents
monthlyAmountIncludingTaxCents
name
slug
taxAmountCents
taxPercentage
}
subscribedPlan {
activatedAt
allowedToCancel
billingCycleInterval
billingCycleIntervalUnit
customInitialAmountCents
customRecurringAmountCents
graceCancellationEndsAt
graceCancellationPeriod
graceCancellationUnit
id
inGraceCancellation
inMinimumCommitment
maximumCommitmentEndsAt
maximumCommitmentPeriod
maximumCommitmentUnit
minimumCommitmentEndsAt
minimumCommitmentPeriod
minimumCommitmentUnit
name
nextBillingDate
showInPriceBreakDown
trialPeriodPeriod
trialPeriodUnit
unconsumedContractTermEventCount
}
}
}
}
`,
variables: {
planSlug: plans[0].slug //
}
})
const requestOptions = {
method: 'POST',
headers,
body: graphql,
};
const response = await fetch("https://portal.firmhouse.com/graphql", requestOptions)
const body = await response.json()
const { subscription } = body.data.updatePlan
Fetching current cart information
You can use getSubscription
query to get the current state of the cart. It's helpful when customers return to the application after leaving. You can use this query to retrieve cart information using the subscription token that you saved in a cookie or local storage. It's also useful for fetching details when they navigate to checkout page.
const headers = new Headers({
'Content-Type': 'application/json',
'X-Project-Access-Token': token,
'X-Subscription-Token': subscriptionToken
})
const graphql = JSON.stringify({
query: `query GetSubscription($token: ID!) {
getSubscription(token: $token) {
amountForStartingSubscriptionCents
monthlyAmountCents
checkoutUrl
currency
metadata
name
lastName
email
address
city
country
orderedProducts {
createdAt
graceCancellationEndsAt
id
interval
intervalUnitOfMeasure
maximumCommitmentEndsAt
metadata
minimumCommitmentEndsAt
priceExcludingTaxCents
priceIncludingTaxCents
productId
quantity
recurring
status
title
totalAmountExcludingTaxCents
totalAmountIncludingTaxCents
totalOrdered
updatedAt
}
}
}
`,
variables: {
token: subscriptionToken
}
})
const requestOptions = {
method: 'POST',
headers,
body: graphql,
};
const response = await fetch("https://portal.firmhouse.com/graphql", requestOptions)
const body = await response.json()
const subscription = body.data.getSubscription
You can see the list of available fields for a subscription here.
Updating address details
To update the subscription details, you can use the updateAddressDetails
mutation. This is helpful for checkout pages where users can enter their payment, invoice, and shipping information.
const headers = new Headers({
"Content-Type": "application/json",
"X-Project-Access-Token": token,
"X-Subscription-Token": subscriptionToken,
});
const graphql = JSON.stringify({
query: `mutation UpdateAddressDetails($input: UpdateAddressDetailsInput!) {
updateAddressDetails(input: $input) {
subscription {
amountForStartingSubscriptionCents
monthlyAmountCents
currency
metadata
name
lastName
email
address
city
country
orderedProducts {
createdAt
graceCancellationEndsAt
id
interval
intervalUnitOfMeasure
maximumCommitmentEndsAt
metadata
minimumCommitmentEndsAt
priceExcludingTaxCents
priceIncludingTaxCents
productId
quantity
recurring
status
title
totalAmountExcludingTaxCents
totalAmountIncludingTaxCents
totalOrdered
updatedAt
}
}
errors {
attribute
message
path
}
}
}
`,
variables: {
input: {
name: "John",
lastName: "Doe",
email: "[email protected]",
address: "X street",
city: "Rotterdam",
country: "NL", // ISO3661
termsAccepted: true,
},
},
});
const requestOptions = {
method: "POST",
headers,
body: graphql,
};
const response = await fetch(
"https://portal.firmhouse.com/graphql",
requestOptions
);
const { data = {}, errors: requestErrors } = await response.json();
const { subscription, errors } = data.updateAddressDetails ?? {};
If there are any validation errors, you can find them in errors
variable. If there is an error in request, you can find them in requestErrors
.
Generating payment links
After you have added the products to your cart and filled in the required information using the updateAddressDetails
mutation, you can use the createSubscriptionFromCart
mutation to create a payment link (paymentUrl
). You can then direct the user to the payment provider with that link to complete the payment process. If the payment is successful, the user will be redirected to the returnUrl
that you provided as parameter for the mutation.
const headers = new Headers({
"Content-Type": "application/json",
"X-Project-Access-Token": token,
"X-Subscription-Token": subscriptionToken,
});
const graphql = JSON.stringify({
query: `mutation CreateSubscriptionFromCart($input: CreateSubscriptionFromCartInput!) {
createSubscriptionFromCart(input: $input){
paymentUrl
returnUrl
errors {
attribute
message
path
}
}
}
`,
variables: {
input: {
returnUrl: "https://yourdomain.com/return", // The URL to redirect to after a successful payment
paymentPageUrl: "https://yourdomain.com/checkout", // The URL where the user can sign up for a new subscription
},
},
});
const requestOptions = {
method: "POST",
headers,
body: graphql,
};
const response = await fetch(
"https://portal.firmhouse.com/graphql",
requestOptions
);
const body = await response.json();
const { paymentUrl, returnUrl, errors } = body.data.createSubscriptionFromCart;
If any required fields are missing, the paymentUrl
will be null and you can check which fields are missing in the errors
variable.
If you haven't added a payment method to your Firmhouse project, this request will fail with a 500 error.
Last updated
Was this helpful?