Cart API Operations
GraphQL Cart Mutations
Create Cart
mutation CreateCart {
createEmptyCart
}
# Returns: cart ID string
Add to Cart
mutation AddToCart($cartId: String!, $sku: String!, $qty: Int!) {
addSimpleProductsToCart(
input: {
cart_id: $cartId
cart_items: [
{ data: { quantity: $qty, sku: $sku } }
]
}
) {
cart {
id
total_quantity
items {
uid
product {
name
sku
price_range {
minimum_price {
regular_price { value currency }
}
}
}
quantity
}
prices {
grand_total { value currency }
subtotal_excluding_tax { value }
discount_amounts { value }
}
}
}
}
Update Cart Item
mutation UpdateCartItem($cartId: String!, $itemId: Int!, $qty: Int!) {
updateSimpleProductsCart(
cart_id: $cartId
cart_items: [
{ cart_item_id: $itemId, quantity: $qty }
]
) {
cart {
items { uid quantity }
prices { grand_total { value } }
}
}
}
Remove Cart Item
mutation RemoveFromCart($cartId: String!, $itemId: Int!) {
removeItemFromCart(
cart_id: $cartId
cart_item_id: $itemId
) {
cart {
items { uid product { name } }
total_quantity
}
}
}
Apply Coupon
mutation ApplyCoupon($cartId: String!, $couponCode: String!) {
applyCouponToCart(
cart_id: $cartId
coupon_code: $couponCode
) {
cart {
prices {
discount_amounts { value }
grand_total { value }
}
}
}
}
Client Implementation
// composables/useCart.ts
import { gql } from '@apollo/client';
import { useMutation } from '@vue/apollo-composable';
const ADD_TO_CART = gql`
mutation AddToCart($cartId: String!, $sku: String!, $qty: Int!) {
addSimpleProductsToCart(
input: {
cart_id: $cartId
cart_items: [{ data: { quantity: $qty, sku: $sku } }]
}
) {
cart {
id
total_quantity
items { uid product { name sku } quantity }
prices { grand_total { value } }
}
}
}
`;
export function useCart() {
const { mutate: addToCart, loading, error } = useMutation(ADD_TO_CART);
const addItem = async (cartId: string, sku: string, qty: number) => {
const { data } = await addToCart({ cartId, sku, qty });
return data.addSimpleProductsToCart.cart;
};
return { addItem, loading, error };
}
Checkout Flow
Checkout Steps
Step 1: Shipping Information
mutation SetShippingAddress(
$cartId: String!
$firstname: String!
$lastname: String!
$street: [String]!
$city: String!
$region: String!
$postcode: String!
$country: String!
$telephone: String!
) {
setShippingAddressesOnCart(
input: {
cart_id: $cartId
shipping_addresses: [
{
address: {
firstname: $firstname
lastname: $lastname
street: $street
city: $city
region: $region
postcode: $postcode
country_code: $country
telephone: $telephone
}
}
]
}
) {
cart {
shipping_addresses {
firstname
lastname
available_shipping_methods {
carrier_code
method_code
carrier_title
method_title
amount { value }
}
}
}
}
}
Step 2: Shipping Method
mutation SetShippingMethod(
$cartId: String!
$carrierCode: String!
$methodCode: String!
) {
setShippingMethodsOnCart(
input: {
cart_id: $cartId
shipping_methods: [
{
carrier_code: $carrierCode
method_code: $methodCode
}
]
}
) {
cart {
shipping_addresses {
selected_shipping_method {
carrier_code
method_code
amount { value }
}
}
prices {
grand_total { value }
}
}
}
}
Step 3: Payment Method
mutation SetPaymentMethod(
$cartId: String!
$paymentMethod: String!
) {
setPaymentMethodOnCart(
input: {
cart_id: $cartId
payment_method: {
code: $paymentMethod
}
}
) {
cart {
available_payment_methods {
code
title
}
selected_payment_method {
code
title
}
}
}
}
Step 4: Place Order
mutation PlaceOrder($cartId: String!) {
placeOrder(
input: {
cart_id: $cartId
}
) {
order {
order_number
order_id
}
}
}
Complete Checkout Flow
// composables/useCheckout.ts
export function useCheckout() {
const { addItem } = useCart();
const checkout = async (cartId: string, checkoutData: any) => {
// 1. Set shipping address
await setShippingAddress(cartId, checkoutData.shipping);
// 2. Set shipping method
await setShippingMethod(
cartId,
checkoutData.shippingMethod.carrierCode,
checkoutData.shippingMethod.methodCode
);
// 3. Set billing address (if different)
if (checkoutData.billingDifferent) {
await setBillingAddress(cartId, checkoutData.billing);
}
// 4. Set payment method
await setPaymentMethod(cartId, checkoutData.paymentMethod);
// 5. Place order
const order = await placeOrder(cartId);
return order;
};
return { checkout };
}
Payment Integration
Payment Gateway Integration
Stripe Integration
// lib/stripe.ts
import Stripe from 'stripe';
const stripe = new Stripe(process.env.STRIPE_SECRET_KEY);
export async function createPaymentIntent(
amount: number,
currency: string
) {
return await stripe.paymentIntents.create({
amount: Math.round(amount * 100), // Convert to cents
currency,
automatic_payment_methods: { enabled: true }
});
}
// Checkout component
export default function StripeCheckout() {
const stripe = useStripe();
const elements = useElements();
const handleSubmit = async () => {
const { error } = await stripe.confirmPayment({
elements,
confirmParams: {
return_url: `${window.location.origin}/order-confirmation`
}
});
if (error) {
console.error(error.message);
}
};
return (
<form onSubmit={handleSubmit}>
<PaymentElement />
<button type="submit">Pay</button>
</form>
);
}
PayPal Integration
// components/PayPalCheckout.tsx
import { PayPalScriptProvider, PayPalButtons } from '@paypal/react-paypal-js';
export default function PayPalCheckout({ amount, onSuccess }) {
return (
<PayPalScriptProvider options={{
'client-id': process.env.PAYPAL_CLIENT_ID
}}>
<PayPalButtons
createOrder={(data, actions) => {
return actions.order.create({
purchase_units: [{
amount: { value: amount.toString() }
}]
});
}}
onApprove={(data, actions) => {
return actions.order.capture().then((details) => {
onSuccess(details.id);
});
}}
/>
</PayPalScriptProvider>
);
}
Custom Payment Handler
// lib/payment-handler.ts
export class PaymentHandler {
private processors: Map<string, PaymentProcessor>;
constructor() {
this.processors = new Map();
this.processors.set('stripe', new StripeProcessor());
this.processors.set('paypal', new PayPalProcessor());
this.processors.set('checkmo', new CheckmoProcessor());
}
async process(
method: string,
cartId: string,
paymentData: any
): Promise<PaymentResult> {
const processor = this.processors.get(method);
if (!processor) {
throw new Error(`Unknown payment method: ${method}`);
}
return processor.process(cartId, paymentData);
}
}
Webhook Handling
// pages/api/webhooks/stripe.ts
import { NextApiRequest, NextApiResponse } from 'next';
import Stripe from 'stripe';
const stripe = new Stripe(process.env.STRIPE_SECRET_KEY);
export default async function handler(
req: NextApiRequest,
res: NextApiResponse
) {
const sig = req.headers['stripe-signature'];
let event: Stripe.Event;
try {
event = stripe.webhooks.constructEvent(
req.body,
sig,
process.env.STRIPE_WEBHOOK_SECRET
);
} catch (err) {
return res.status(400).send(`Webhook Error: ${err.message}`);
}
switch (event.type) {
case 'payment_intent.succeeded':
const paymentIntent = event.data.object;
await handlePaymentSuccess(paymentIntent);
break;
case 'payment_intent.payment_failed':
await handlePaymentFailure(event.data.object);
break;
}
res.status(200).json({ received: true });
}
Session Handling
Session Management
Persistent Cart
// Store cart ID in localStorage
export function getCartId(): string | null {
return localStorage.getItem('cart_id');
}
export function setCartId(cartId: string): void {
localStorage.setItem('cart_id', cartId);
}
// Restore cart on page load
export async function restoreCart(): Promise<string> {
let cartId = getCartId();
if (cartId) {
// Verify cart still exists
try {
const cart = await getCart(cartId);
return cartId;
} catch {
// Cart expired or invalid
localStorage.removeItem('cart_id');
}
}
// Create new cart
const newCartId = await createCart();
setCartId(newCartId);
return newCartId;
}
Guest vs Customer Cart
// Merge guest cart with customer cart on login
export async function mergeGuestCart(customerCartId: string) {
const guestCartId = getCartId();
if (!guestCartId) return;
// Get guest cart items
const guestCart = await getCart(guestCartId);
// Add each item to customer cart
for (const item of guestCart.items) {
await addItemToCart(customerCartId, item.sku, item.quantity);
}
// Clear guest cart
localStorage.removeItem('cart_id');
}
Session Timeout
// Auto-expire cart after inactivity
const CART_TIMEOUT = 30 * 60 * 1000; // 30 minutes
let cartTimer: NodeJS.Timeout;
export function resetCartTimer() {
clearTimeout(cartTimer);
cartTimer = setTimeout(() => {
// Clear cart on timeout
localStorage.removeItem('cart_id');
// Optionally notify user
}, CART_TIMEOUT);
}
// Reset on user activity
['click', 'keydown', 'scroll'].forEach(event => {
document.addEventListener(event, resetCartTimer);
});
Cross-Tab Synchronization
// Sync cart across browser tabs
window.addEventListener('storage', (event) => {
if (event.key === 'cart_id') {
// Cart changed in another tab
if (event.newValue) {
// Refresh cart display
refreshCart();
} else {
// Cart cleared
redirectToHome();
}
}
});
Server-Side Session
// For SSR, use cookies
import { serialize, parse } from 'cookie';
// Set cart cookie
export function setCartCookie(res, cartId) {
res.setHeader('Set-Cookie', serialize('cart_id', cartId, {
httpOnly: true,
secure: process.env.NODE_ENV === 'production',
sameSite: 'lax',
path: '/',
maxAge: 60 * 60 * 24 * 7 // 1 week
}));
}
// Get cart from cookie
export function getCartFromCookie(req) {
const cookies = parse(req.headers.cookie || '');
return cookies.cart_id;
}
Practice Problems
Implement a complete headless checkout flow with shipping, payment, and order placement.
Integrate Stripe payment gateway with a headless Magento checkout.
Quiz
1. What GraphQL mutation creates an empty cart?
2. How to persist cart across page refreshes?
3. What is the checkout flow order?
4. How to merge guest and customer carts?
Flashcards
Question
What creates an empty cart?
Click to reveal answer
Answer
createEmptyCart GraphQL mutation
Question
How to persist cart?
Click to reveal answer
Answer
Store cart ID in localStorage or httpOnly cookie
Question
Checkout flow order?
Click to reveal answer
Answer
Shipping → Payment → Place Order
Question
How to merge carts?
Click to reveal answer
Answer
Add guest items to customer cart on login
Question
What handles payment webhooks?
Click to reveal answer
Answer
API route that verifies signature and processes event
Revision Notes
Key Takeaways
- 1. Cart operations: createEmptyCart, addSimpleProductsToCart, updateSimpleProductsCart, removeItemFromCart
- 2. Checkout flow: shipping address → shipping method → payment method → place order
- 3. Payment: integrate via client-side SDK (Stripe) or redirect (PayPal)
- 4. Persist cart in localStorage (client) or cookie (SSR)
- 5. Merge guest cart with customer cart on login
- 6. Handle payment webhooks for async confirmation
Interview Tips
- • Walk through the headless checkout flow
- • How do you handle payment in a headless app?
- • How do you persist cart across sessions?
- • What happens to guest cart when customer logs in?
- • How do you handle payment webhooks?
Cheat Sheet
Headless Checkout Cheat Sheet
Cart Mutations:
- createEmptyCart
- addSimpleProductsToCart
- updateSimpleProductsCart
- removeItemFromCart
- applyCouponToCart
Checkout Steps:
- setShippingAddressesOnCart
- setShippingMethodsOnCart
- setPaymentMethodOnCart
- placeOrder
Session:
- localStorage: cart ID
- Cookie: httpOnly for SSR
- Merge on login
Payment:
- Client-side SDK (Stripe)
- Redirect (PayPal)
- Webhooks for confirmation