Current section
Files
Jump to
Current section
Files
src/storefront/cart.gleam
import gleam/dynamic/decode
import gleam/javascript/promise
import gleam/json
import gleam/list
import gleam/option.{type Option, None, Some}
import storefront.{type ShopifyError, type StorefrontApiClientConfig, handler}
import storefront/fragments.{cart_fragment}
import storefront/utils.{
type Connection, type Cost, type Edge, type Image, type Money,
type SelectedOption, Connection, Edge, cost_decoder, image_decoder,
money_decoder, remove_edges_and_nodes, selected_option_decoder,
}
import wisp.{type Request}
pub const cookie_name = "cartId"
pub fn create_cart(
config: StorefrontApiClientConfig,
on_result: fn(Result(Cart, ShopifyError)) -> Nil,
) -> Nil {
let handler = handler(config)
handler.fetch(
create_cart_mutation,
None,
shopify_create_cart_mutation_decoder(),
)
|> promise.map(fn(res) {
case res {
Ok(cart) -> Ok(reshape_cart(cart.data.cart_create.cart))
Error(err) -> Error(err)
}
})
|> promise.tap(on_result)
Nil
}
pub fn get_cart(
config: StorefrontApiClientConfig,
req: Request,
on_result: fn(Result(Cart, ShopifyError)) -> Nil,
) -> Nil {
let client = handler(config)
case wisp.get_cookie(req, cookie_name, wisp.Signed) {
Error(Nil) -> Nil
Ok(id) -> {
let query = get_cart_query
let variables = json.object([#("cartId", json.string(id))])
let fetch =
client.fetch(query, Some(variables), shopify_cart_operation_decoder())
promise.map_try(fetch, fn(res) {
let cart = res.data.cart
case cart {
Some(cart) -> {
let x = reshape_cart(cart)
Ok(x)
}
None -> Error(storefront.NetworkError)
}
})
|> promise.tap(on_result)
Nil
}
}
}
pub fn add_to_cart(
config: StorefrontApiClientConfig,
lines: List(CartItem),
req: Request,
on_result: fn(Result(Cart, ShopifyError)) -> Nil,
) -> Nil {
let client = handler(config)
case wisp.get_cookie(req, cookie_name, wisp.Signed) {
Error(Nil) -> create_cart(config, on_result)
Ok(id) -> {
let query = add_to_cart_mutation
let lines_json =
lines
|> list.map(fn(line) {
shopify_add_to_cart_operation_variables_lines_to_json(
ShopifyAddToCartOperationVariablesLines(
merchandise_id: line.merchandise.id,
quantity: line.quantity,
),
)
})
let variables =
json.object([
#("cartId", json.string(id)),
#("lines", lines_json |> json.preprocessed_array),
])
let fetch =
client.fetch(
query,
Some(variables),
shopify_add_to_cart_operation_decoder(),
)
promise.map_try(fetch, fn(res) {
let cart = Ok(reshape_cart(res.data.cart))
cart
})
|> promise.tap(on_result)
Nil
}
}
}
pub fn remove_from_cart(
config: StorefrontApiClientConfig,
lines_ids: List(String),
req: Request,
on_result: fn(Result(Cart, ShopifyError)) -> Nil,
) -> Nil {
let client = handler(config)
case wisp.get_cookie(req, cookie_name, wisp.Signed) {
Error(Nil) -> create_cart(config, on_result)
Ok(id) -> {
let query = add_to_cart_mutation
let variables =
json.object([
#("cartId", json.string(id)),
#("lineIds", json.array(lines_ids, of: json.string)),
])
let fetch =
client.fetch(
query,
Some(variables),
shopify_remove_from_cart_operation_decoder(),
)
promise.map_try(fetch, fn(res) {
let cart = Ok(reshape_cart(res.data.cart_lines_remove.cart))
cart
})
|> promise.tap(on_result)
Nil
}
}
}
pub fn update_cart(
config: StorefrontApiClientConfig,
lines: List(CartItem),
req: Request,
on_result: fn(Result(Cart, ShopifyError)) -> Nil,
) {
let client = handler(config)
case wisp.get_cookie(req, cookie_name, wisp.Signed) {
Error(Nil) -> create_cart(config, on_result)
Ok(id) -> {
let query = edit_cart_mutation
let lines_json = {
lines
|> list.map(fn(line) {
shopify_update_cart_line_update_to_json(ShopifyUpdateCartLineUpdate(
id:,
merchandise_id: line.merchandise.id,
quantity: line.quantity,
))
})
}
let variables =
json.object([
#("cartId", json.string(id)),
#("lines", lines_json |> json.preprocessed_array),
])
let fetch =
client.fetch(
query,
Some(variables),
shopify_update_cart_operation_decoder(),
)
promise.map_try(fetch, fn(res) {
Ok(reshape_cart(res.data.cart_lines_update.cart))
})
|> promise.tap(on_result)
Nil
}
}
}
pub fn reshape_cart(cart: ShopifyCart) -> Cart {
let result = {
case cart.cost {
None -> None
Some(cost) -> {
let lines = remove_edges_and_nodes(cart.lines)
Some(Cart(
checkout_url: cart.checkout_url,
id: cart.id,
lines: lines,
total_quantity: cart.total_quantity,
cost: Some(ShopifyCartCost(
subtotal_amount: cost.subtotal_amount,
total_tax_amount: cost.total_tax_amount,
total_amount: cost.total_amount,
)),
))
}
}
}
let lines = remove_edges_and_nodes(cart.lines)
option.unwrap(
result,
Cart(
checkout_url: cart.checkout_url,
id: cart.id,
lines: lines,
total_quantity: cart.total_quantity,
cost: cart.cost,
),
)
}
pub const add_to_cart_mutation = "
mutation addToCart($cartId: ID!, $lines: [CartLineInput!]!) {
cartLinesAdd(cartId: $cartId, lines: $lines) {
cart {
...cart
}
}
}
"
<> cart_fragment
pub opaque type CartLinesAdd {
CartLinesAdd(cart: ShopifyCart)
}
fn cart_lines_add_decoder() -> decode.Decoder(CartLinesAdd) {
use cart <- decode.field("cart", shopify_cart_decoder())
decode.success(CartLinesAdd(cart:))
}
type ShopifyAddToCartOperation {
ShopifyAddToCartMutation(
data: CartLinesAdd,
variables: List(ShopifyAddToCartOperationVariables),
)
}
fn shopify_add_to_cart_operation_decoder() -> decode.Decoder(
ShopifyAddToCartOperation,
) {
use data <- decode.field("data", cart_lines_add_decoder())
use variables <- decode.field(
"variables",
decode.list(shopify_add_to_cart_operation_variables_decoder()),
)
decode.success(ShopifyAddToCartMutation(data:, variables:))
}
fn shopify_add_to_cart_operation_variables_decoder() -> decode.Decoder(
ShopifyAddToCartOperationVariables,
) {
use cart_id <- decode.field("cart_id", decode.string)
use lines <- decode.field(
"lines",
shopify_add_to_cart_operation_variables_lines_decoder(),
)
decode.success(ShopifyAddToCartOperationVariables(cart_id:, lines:))
}
type ShopifyAddToCartOperationVariablesLines {
ShopifyAddToCartOperationVariablesLines(merchandise_id: String, quantity: Int)
}
fn shopify_add_to_cart_operation_variables_lines_to_json(
shopify_add_to_cart_operation_variables_lines: ShopifyAddToCartOperationVariablesLines,
) -> json.Json {
let ShopifyAddToCartOperationVariablesLines(merchandise_id:, quantity:) =
shopify_add_to_cart_operation_variables_lines
json.object([
#("merchandise_id", json.string(merchandise_id)),
#("quantity", json.int(quantity)),
])
}
fn shopify_add_to_cart_operation_variables_lines_decoder() -> decode.Decoder(
ShopifyAddToCartOperationVariablesLines,
) {
use merchandise_id <- decode.field("merchandise_id", decode.string)
use quantity <- decode.field("quantity", decode.int)
decode.success(ShopifyAddToCartOperationVariablesLines(
merchandise_id:,
quantity:,
))
}
type ShopifyAddToCartOperationVariables {
ShopifyAddToCartOperationVariables(
cart_id: String,
lines: ShopifyAddToCartOperationVariablesLines,
)
}
pub const create_cart_mutation = "
mutation createCart($lineItems: [CartLineInput!]) {
cartCreate(input: { lines: $lineItems }) {
cart {
...cart
}
}
}
"
<> cart_fragment
type ShopifyCreateCartMutation {
ShopifyCreateCartMutation(data: ShopifyCreateCartData)
}
fn shopify_create_cart_mutation_decoder() -> decode.Decoder(
ShopifyCreateCartMutation,
) {
use data <- decode.field("data", shopify_create_cart_data_decoder())
decode.success(ShopifyCreateCartMutation(data:))
}
type ShopifyCreateCartData {
ShopifyCreateCartData(cart_create: CreateCart)
}
fn shopify_create_cart_data_decoder() -> decode.Decoder(ShopifyCreateCartData) {
use cart_create <- decode.field("cartCreate", create_cart_decoder())
decode.success(ShopifyCreateCartData(cart_create:))
}
type CreateCart {
CreateCart(cart: ShopifyCart)
}
fn create_cart_decoder() -> decode.Decoder(CreateCart) {
use cart <- decode.field("cart", shopify_cart_decoder())
decode.success(CreateCart(cart:))
}
// REMOVE CART MUTATIONS AND DECODERS
pub const remove_from_cart_mutation = "
mutation removeFromCart($cartId: ID!, $lineIds: [ID!]!) {
cartLinesRemove(cartId: $cartId, lineIds: $lineIds) {
cart {
...cart
}
}
}
"
<> cart_fragment
type ShopifyRemoveFromCartOperation {
ShopifyRemoveFromCartOperation(
data: RemoveOperationData,
variables: ShopifyRemoveFromCartVariables,
)
}
fn shopify_remove_from_cart_operation_decoder() -> decode.Decoder(
ShopifyRemoveFromCartOperation,
) {
use data <- decode.field("data", remove_operation_data_decoder())
use variables <- decode.field(
"variables",
shopify_remove_from_cart_variables_decoder(),
)
decode.success(ShopifyRemoveFromCartOperation(data:, variables:))
}
type ShopifyRemoveFromCartVariables {
ShopifyRemoveFromCartVariables(cart_id: String, line_ids: List(String))
}
fn shopify_remove_from_cart_variables_decoder() -> decode.Decoder(
ShopifyRemoveFromCartVariables,
) {
use cart_id <- decode.field("cartId", decode.string)
use line_ids <- decode.field("lineIds", decode.list(decode.string))
decode.success(ShopifyRemoveFromCartVariables(cart_id:, line_ids:))
}
type RemoveOperationData {
RemoveOperationData(cart_lines_remove: CartLinesRemove)
}
fn remove_operation_data_decoder() -> decode.Decoder(RemoveOperationData) {
use cart_lines_remove <- decode.field(
"cartLinesRemove",
cart_lines_remove_decoder(),
)
decode.success(RemoveOperationData(cart_lines_remove:))
}
type CartLinesRemove {
CartLinesRemove(cart: ShopifyCart)
}
fn cart_lines_remove_decoder() -> decode.Decoder(CartLinesRemove) {
use cart <- decode.field("cart", shopify_cart_decoder())
decode.success(CartLinesRemove(cart:))
}
pub const edit_cart_mutation = "
mutation editCartItems($cartId: ID!, $lines: [CartLineUpdateInput!]!) {
cartLinesUpdate(cartId: $cartId, lines: $lines) {
cart {
...cart
}
}
}
"
<> cart_fragment
type ShopifyUpdateCartOperation {
ShopifyUpdateCartOperation(
data: UpdateOperationData,
variables: ShopifyUpdateCartVariables,
)
}
fn shopify_update_cart_operation_decoder() -> decode.Decoder(
ShopifyUpdateCartOperation,
) {
use data <- decode.field("data", update_operation_data_decoder())
use variables <- decode.field(
"variables",
shopify_update_cart_variables_decoder(),
)
decode.success(ShopifyUpdateCartOperation(data:, variables:))
}
type UpdateOperationData {
UpdateOperationData(cart_lines_update: CartLinesUpdate)
}
fn update_operation_data_decoder() -> decode.Decoder(UpdateOperationData) {
use cart_lines_update <- decode.field(
"cartLinesUpdate",
cart_lines_update_decoder(),
)
decode.success(UpdateOperationData(cart_lines_update:))
}
type CartLinesUpdate {
CartLinesUpdate(cart: ShopifyCart)
}
fn cart_lines_update_decoder() -> decode.Decoder(CartLinesUpdate) {
use cart <- decode.field("cart", shopify_cart_decoder())
decode.success(CartLinesUpdate(cart:))
}
type ShopifyUpdateCartVariables {
ShopifyUpdateCartVariables(
cart_id: String,
lines: List(ShopifyUpdateCartLineUpdate),
)
}
fn shopify_update_cart_variables_decoder() -> decode.Decoder(
ShopifyUpdateCartVariables,
) {
use cart_id <- decode.field("cartId", decode.string)
use lines <- decode.field(
"lines",
decode.list(shopify_update_cart_line_update_decoder()),
)
decode.success(ShopifyUpdateCartVariables(cart_id:, lines:))
}
type ShopifyUpdateCartLineUpdate {
ShopifyUpdateCartLineUpdate(id: String, merchandise_id: String, quantity: Int)
}
fn shopify_update_cart_line_update_to_json(
shopify_update_cart_line_update: ShopifyUpdateCartLineUpdate,
) -> json.Json {
let ShopifyUpdateCartLineUpdate(id:, merchandise_id:, quantity:) =
shopify_update_cart_line_update
json.object([
#("id", json.string(id)),
#("merchandiseId", json.string(merchandise_id)),
#("quantity", json.int(quantity)),
])
}
fn shopify_update_cart_line_update_decoder() -> decode.Decoder(
ShopifyUpdateCartLineUpdate,
) {
use id <- decode.field("id", decode.string)
use merchandise_id <- decode.field("merchandiseId", decode.string)
use quantity <- decode.field("quantity", decode.int)
decode.success(ShopifyUpdateCartLineUpdate(id:, merchandise_id:, quantity:))
}
pub const get_cart_query = "
query getCart($cartId: ID!) {
cart(id: $cartId) {
...cart
}
}
"
<> cart_fragment
pub opaque type ShopifyCartOperation {
ShopifyCartOperation(
data: ShopifyCartOperationData,
variables: ShopifyCartOperationVariables,
)
}
fn shopify_cart_operation_decoder() -> decode.Decoder(ShopifyCartOperation) {
use data <- decode.field("data", shopify_cart_operation_data_decoder())
use variables <- decode.field(
"variables",
shopify_cart_operation_variables_decoder(),
)
decode.success(ShopifyCartOperation(data:, variables:))
}
type ShopifyCartOperationData {
ShopifyCartOperationData(cart: Option(ShopifyCart))
}
fn shopify_cart_operation_data_decoder() -> decode.Decoder(
ShopifyCartOperationData,
) {
use cart <- decode.field("cart", decode.optional(shopify_cart_decoder()))
decode.success(ShopifyCartOperationData(cart:))
}
type ShopifyCartOperationVariables {
ShopifyCartOperationVariables(cart_id: String)
}
fn shopify_cart_operation_variables_decoder() -> decode.Decoder(
ShopifyCartOperationVariables,
) {
use cart_id <- decode.field("cartId", decode.string)
decode.success(ShopifyCartOperationVariables(cart_id:))
}
pub type Cart {
Cart(
id: Option(String),
checkout_url: String,
cost: Option(ShopifyCartCost),
total_quantity: Int,
lines: List(CartItem),
)
}
pub fn cart_decoder() -> decode.Decoder(Cart) {
use id <- decode.field("id", decode.optional(decode.string))
use checkout_url <- decode.field("checkoutUrl", decode.string)
use cost <- decode.field("cost", decode.optional(shopify_cart_cost_decoder()))
use total_quantity <- decode.field("totalQuantity", decode.int)
use lines <- decode.field("lines", decode.list(cart_item_decoder()))
decode.success(Cart(id:, checkout_url:, cost:, total_quantity:, lines:))
}
pub type ShopifyCart {
ShopifyCart(
id: Option(String),
checkout_url: String,
cost: Option(ShopifyCartCost),
lines: Connection(CartItem),
total_quantity: Int,
)
}
fn cart_item_edge_decoder() -> decode.Decoder(Edge(CartItem)) {
use node <- decode.field("node", cart_item_decoder())
decode.success(Edge(node:))
}
fn cart_item_connection_decoder() -> decode.Decoder(Connection(CartItem)) {
use edges <- decode.field("edges", decode.list(cart_item_edge_decoder()))
decode.success(Connection(edges:))
}
fn shopify_cart_decoder() -> decode.Decoder(ShopifyCart) {
use id <- decode.field("id", decode.optional(decode.string))
use checkout_url <- decode.field("checkoutUrl", decode.string)
use cost <- decode.field("cost", decode.optional(shopify_cart_cost_decoder()))
use lines <- decode.field("lines", cart_item_connection_decoder())
use total_quantity <- decode.field("totalQuantity", decode.int)
decode.success(ShopifyCart(id:, checkout_url:, cost:, lines:, total_quantity:))
}
pub type ShopifyCartCost {
ShopifyCartCost(
total_amount: Money,
subtotal_amount: Money,
total_tax_amount: Money,
)
}
fn shopify_cart_cost_decoder() -> decode.Decoder(ShopifyCartCost) {
use total_amount <- decode.field("totalAmount", money_decoder())
use subtotal_amount <- decode.field("subtotalAmount", money_decoder())
use total_tax_amount <- decode.field("totalTaxAmount", money_decoder())
decode.success(ShopifyCartCost(
total_amount:,
subtotal_amount:,
total_tax_amount:,
))
}
pub type CartItem {
CartItem(
id: Option(String),
quantity: Int,
cost: Cost,
merchandise: Merchandise,
)
}
fn cart_item_decoder() -> decode.Decoder(CartItem) {
use id <- decode.field("id", decode.optional(decode.string))
use quantity <- decode.field("quantity", decode.int)
use cost <- decode.field("cost", cost_decoder())
use merchandise <- decode.field("merchandise", merchandise_decoder())
decode.success(CartItem(id:, quantity:, cost:, merchandise:))
}
pub type Merchandise {
Merchandise(
id: String,
title: String,
selected_options: List(SelectedOption),
product: CartProduct,
)
}
pub fn merchandise_decoder() -> decode.Decoder(Merchandise) {
use id <- decode.field("id", decode.string)
use title <- decode.field("title", decode.string)
use selected_options <- decode.field(
"selectedOptions",
decode.list(selected_option_decoder()),
)
use product <- decode.field("product", cart_product_decoder())
decode.success(Merchandise(id:, title:, selected_options:, product:))
}
pub type CartProduct {
CartProduct(id: String, handle: String, title: String, featured_image: Image)
}
fn cart_product_decoder() -> decode.Decoder(CartProduct) {
use id <- decode.field("id", decode.string)
use handle <- decode.field("handle", decode.string)
use title <- decode.field("title", decode.string)
use featured_image <- decode.field("featuredImage", image_decoder())
decode.success(CartProduct(id:, handle:, title:, featured_image:))
}