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:
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 productsquery. This query gives you all the information about the products you have set up in the Firmhouse Portal.
You can use after and first parameters to implement pagination. after variable expects a cursor which you can get from pageInfo.endCursor value from previous call.
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.
We will pass the value of subscriptionToken in the X-Subscription-Token header for all subsequent calls. If you are creating a web app, you can store this value in a cookie or localStorage so that your customers can pick up where they left off when they return.
We will pass the value of token as a parameter for all subsequent calls. If you are creating a web app, you can store this value in a cookie or localStorage as shown in the example so that your customers can pick up where they left off when they return. getOrCreate method will return the existing cart if the token is provided and the cart has not been checked out yet, otherwise it will create a new cart.
Note that, if the default plan of your project includes products, they will be automatically added to the cart when you create it.
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.
constheaders=newHeaders({'Content-Type':'application/json','X-Project-Access-Token': token,'X-Subscription-Token': subscriptionToken})constgraphql=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, } }})constrequestOptions= { method:'POST', headers, body: graphql,};constresponse=awaitfetch("https://portal.firmhouse.com/graphql", requestOptions)constbody=awaitresponse.json()const { orderedProduct,subscription,errors } =body.data.createOrderedProduct
If you need to add metadata to the ordered product, you must use a project access token with Write permissions. You can add it using the metadata input variable. If you need to use different metadata for multiple orders of the same product, you can set the ensureNewRecord value to true. This will ensure that each time you use the endpoint, a new orderedProduct item is created with the specified metadata.
There are additional options for querying the product to add apart from productId. You can find all these options here.
If you need to add metadata to the ordered product, you need to initialize firmhouse client with write permissions. You can add it using the metadata input variable. If you need to use different metadata for multiple orders of the same product, you can set the ensureNewRecord value to true. This will ensure that each time you use the endpoint, a new orderedProduct item is created with the specified metadata.
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.
constheaders=newHeaders({'Content-Type':'application/json','X-Project-Access-Token': token,'X-Subscription-Token': subscriptionToken})constgraphql=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 } }})constrequestOptions= { method:'POST', headers, body: graphql,};constresponse=awaitfetch("https://portal.firmhouse.com/graphql", requestOptions)const { errors,data } =awaitresponse.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.
constheaders=newHeaders({'Content-Type':'application/json','X-Project-Access-Token': token,'X-Subscription-Token': subscriptionToken})constgraphql=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 } } }})constrequestOptions= { method:'POST', headers, body: graphql,};constresponse=awaitfetch("https://portal.firmhouse.com/graphql", requestOptions)constbody=awaitresponse.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.
You can use getSubscriptionquery 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.
constheaders=newHeaders({'Content-Type':'application/json','X-Project-Access-Token': token,'X-Subscription-Token': subscriptionToken})constgraphql=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 }})constrequestOptions= { method:'POST', headers, body: graphql,};constresponse=awaitfetch("https://portal.firmhouse.com/graphql", requestOptions)constbody=awaitresponse.json()constsubscription=body.data.getSubscription
You can see the list of available fields for a subscription here.
If you want to use the default checkout page, you can redirect your customers tosubscription.checkoutUrl
Updating address details
To update the subscription details, you can use the updateAddressDetailsmutation. This is helpful for checkout pages where users can enter their payment, invoice, and shipping information.
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.
By default, only the following fields are required to proceed to the payment stage: name, email, address, city, country. You can find the complete list of available input variables here.
If you want to change the required fields, you can do so on the Checkout > Preferences > Customer fields page in your Firmhouse project.
Generating payment links
After you have added the products to your cart and filled in the required information using the updateAddressDetailsmutation, you can use the createSubscriptionFromCartmutation 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.
constheaders=newHeaders({"Content-Type":"application/json","X-Project-Access-Token": token,"X-Subscription-Token": subscriptionToken,});constgraphql=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 }, },});constrequestOptions= { method:"POST", headers, body: graphql,};constresponse=awaitfetch("https://portal.firmhouse.com/graphql", requestOptions);constbody=awaitresponse.json();const { paymentUrl,returnUrl,errors } =body.data.createSubscriptionFromCart;