πŸ”Œ Cart API

Add detailing services and products to the Pops Personal Touch cart from any external page.

Overview

Three integration methods are supported. All write to the shared browser cart used by /services, /products, and /estimate.

  • URL query params β€” redirect users to the site with items pre-added.
  • postMessage β€” from an embedded iframe or opener window.
  • Global helper β€” window.PopsCart when embedded on the same origin.

πŸ§ͺ Try it live

Pick services and products, then see the resulting cart state β€” computed by /api/cart and, if you want, applied to your real browser cart.

Services

Products

Open add-URL in new tab β†—Open remove-URL in new tab β†—Open clear-URL in new tab β†—

Preview URL

https://pops-personal-touch.lovable.app/?addService=auto-detailing&addProduct=foam-shampoo%3A2

Equivalent curl (GET /api/cart)

curl -s "https://pops-personal-touch.lovable.app/api/cart?addService=auto-detailing&addProduct=foam-shampoo%3A2" | jq

Equivalent curl (POST /api/cart)

curl -X POST "https://pops-personal-touch.lovable.app/api/cart" \
  -H "Content-Type: application/json" \
  -d '{   "services": [     "auto-detailing"   ],   "products": [     {       "id": "foam-shampoo",       "qty": 2     }   ] }'

Computed cart

{
  "services": [
    {
      "id": "auto-detailing",
      "kind": "service",
      "title": "Auto Detailing",
      "icon": "πŸš—",
      "qty": 1,
      "unitPrice": 199,
      "lineTotal": 199,
      "meta": {
        "durationMin": 60,
        "rate": 199
      }
    }
  ],
  "products": [
    {
      "id": "foam-shampoo",
      "kind": "product",
      "title": "pH-Neutral Foam Car Shampoo (1 gal)",
      "icon": "🧴",
      "qty": 2,
      "unitPrice": 29.99,
      "lineTotal": 59.98
    }
  ],
  "subtotal": 258.98,
  "tax": 15.54,
  "taxRate": 0.06,
  "total": 274.52,
  "itemCount": 3,
  "unknownIds": []
}

1. URL Query Parameters

Link users directly to any page with addService and/or addProduct. Multiple IDs are comma-separated. Product quantity uses id:qty.

Parameters

NameTypeDescription
addServicestringComma-separated service IDs (e.g. auto-detailing,interior-detail)
addProductstringComma-separated id:qty pairs (e.g. foam-shampoo:2,tire-shine:1). Qty defaults to 1.

Clickable Examples

Click any link below to open the site with those items pre-added to your cart.

Raw URL Template

https://pops-personal-touch.lovable.app/<route>?addService=<id,id,...>&addProduct=<id:qty,id:qty,...>

HTML Link Snippet

<a href="https://pops-personal-touch.lovable.app/?addService=auto-detailing&addProduct=foam-shampoo:2">
  Add to Pops Cart
</a>

curl β€” verify each URL scenario

The URL params live on the client (they mutate localStorage), but you can hit GET /api/cart with the same params to see exactly what those items would cost:

curl -s "https://pops-personal-touch.lovable.app/api/cart?addService=auto-detailing"                        | jq
curl -s "https://pops-personal-touch.lovable.app/api/cart?addService=auto-detailing,interior-detail,exterior-detail"           | jq
curl -s "https://pops-personal-touch.lovable.app/api/cart?addProduct=foam-shampoo:2"                      | jq
curl -s "https://pops-personal-touch.lovable.app/api/cart?addProduct=foam-shampoo:2,tire-shine:1"            | jq
curl -s "https://pops-personal-touch.lovable.app/api/cart?addService=auto-detailing,interior-detail&addProduct=foam-shampoo:2,tire-shine:1" | jq

1b. Remove & Clear via URL

Same URL-param mechanism, but for tearing the cart back down β€” perfect for testing empty-to-filled flows.

Parameters

NameTypeDescription
removeServicestringComma-separated service IDs to remove from the live cart.
removeProductstringComma-separated product IDs to remove (any qty for that ID is dropped).
clearCart1 / trueEmpty the entire cart (services + products).

Order in a single URL: clearCart β†’ remove* β†’ add*. So you can reset & seed in one hop.

Clickable Examples

Raw URL Templates

# Clear entire cart
https://pops-personal-touch.lovable.app/<route>?clearCart=1

# Remove specific IDs
https://pops-personal-touch.lovable.app/<route>?removeService=<id,id,...>&removeProduct=<id,id,...>

# Reset & seed in one URL
https://pops-personal-touch.lovable.app/<route>?clearCart=1&addService=<id,...>&addProduct=<id:qty,...>

curl β€” preview a remove / clear against /api/cart

/api/cart is stateless. Pass baseServices / baseProducts to describe the starting cart, then removeService / removeProduct to preview the resulting summary:

# Clear preview β†’ empty summary
curl -s "https://pops-personal-touch.lovable.app/api/cart?clearCart=1" | jq

# Remove preview: start with auto-detailing + foam-shampoo:2, remove auto-detailing
curl -s "https://pops-personal-touch.lovable.app/api/cart?baseServices=auto-detailing,interior-detail&baseProducts=foam-shampoo:2&removeService=auto-detailing" | jq

# Remove multiple products
curl -s "https://pops-personal-touch.lovable.app/api/cart?baseProducts=foam-shampoo:2,tire-shine:1&removeProduct=foam-shampoo,tire-shine" | jq

# Same operations via POST
curl -X POST "https://pops-personal-touch.lovable.app/api/cart" \
  -H "Content-Type: application/json" \
  -d '{ "action":"remove", "baseServices":["auto-detailing","interior-detail"], "baseProducts":[{"id":"foam-shampoo","qty":2}], "removeServices":["auto-detailing"] }'

curl -X POST "https://pops-personal-touch.lovable.app/api/cart" \
  -H "Content-Type: application/json" \
  -d '{ "action":"clear" }'

postMessage equivalents

// Remove specific IDs
frame.contentWindow.postMessage({
  source: 'pops-cart',
  action: 'remove',
  services: ['auto-detailing'],
  products: [{ id: 'foam-shampoo' }],
}, 'https://pops-personal-touch.lovable.app');

// Clear everything
frame.contentWindow.postMessage({
  source: 'pops-cart',
  action: 'clear',
}, 'https://pops-personal-touch.lovable.app');

2. postMessage API

From a parent window (with the site in an iframe) or an opener/opened window, send a message event with source: "pops-cart".

Message Envelope

{
  "source": "pops-cart",
  "action": "add" | "remove" | "clear" | "get",
  "services": ["service-id-1", "service-id-2"],
  "products": [{ "id": "product-id", "qty": 2 }],
  "requestId": "optional-correlation-id"
}

Actions

ActionPayloadResponse
addservices and/or products{ ok, addedServices, addedProducts }
removeservices and/or products{ ok: true }
clearβ€”{ ok: true }
getβ€”{ services, products }

Example β€” from a parent page hosting an iframe

<iframe id="pops" src="https://pops-personal-touch.lovable.app/" style="width:100%;height:600px;border:0"></iframe>
<script>
  const frame = document.getElementById('pops');
  frame.addEventListener('load', () => {
    frame.contentWindow.postMessage({
      source: 'pops-cart',
      action: 'add',
      services: ['auto-detailing'],
      products: [{ id: 'foam-shampoo', qty: 2 }],
      requestId: 'req-1'
    }, 'https://pops-personal-touch.lovable.app');
  });

  window.addEventListener('message', (e) => {
    if (e.data?.source === 'pops-cart' && e.data.requestId === 'req-1') {
      console.log('Cart response:', e.data.result);
    }
  });
</script>

3. Global Helper (same-origin)

When your script runs inside the site, window.PopsCart is available.

window.PopsCart.add({
  services: ['auto-detailing', 'interior-detail'],
  products: [{ id: 'foam-shampoo', qty: 2 }],
});

window.PopsCart.get();     // { services, products }
window.PopsCart.remove({ services: ['interior-detail'] });
window.PopsCart.clear();

4. GET /api/cart β€” JSON summary

Server endpoint that computes cart contents, line totals, tax (6%), and grand total from the same query params as the URL API. Stateless β€” it does not read anyone's browser cart; it just prices the IDs you send.

Endpoint

GET  https://pops-personal-touch.lovable.app/api/cart?addService=<id,id>&addProduct=<id:qty,id:qty>
POST https://pops-personal-touch.lovable.app/api/cart    Content-Type: application/json
     body: { "services": ["id"], "products": [{ "id": "x", "qty": 2 }] }

Response shape

{
  "ok": true,
  "input":    { "services": ["auto-detailing"], "products": [{ "id": "foam-shampoo", "qty": 2 }] },
  "services": [{ "id","kind":"service","title","icon","qty","unitPrice","lineTotal","meta":{"durationMin","rate"} }],
  "products": [{ "id","kind":"product","title","icon","qty","unitPrice","lineTotal" }],
  "subtotal": 258.98,
  "tax":      15.54,
  "taxRate":  0.06,
  "total":    274.52,
  "itemCount": 3,
  "unknownIds": []
}

curl β€” URL params (GET)

# Single service
curl "https://pops-personal-touch.lovable.app/api/cart?addService=auto-detailing"

# Multiple services
curl "https://pops-personal-touch.lovable.app/api/cart?addService=auto-detailing,interior-detail,exterior-detail"

# Product with quantity
curl "https://pops-personal-touch.lovable.app/api/cart?addProduct=foam-shampoo:2"

# Multiple products
curl "https://pops-personal-touch.lovable.app/api/cart?addProduct=foam-shampoo:2,tire-shine:1,ceramic-wax:3"

# Mixed services + products
curl "https://pops-personal-touch.lovable.app/api/cart?addService=auto-detailing,interior-detail&addProduct=foam-shampoo:2,tire-shine:1"

# Pretty-print with jq
curl -s "https://pops-personal-touch.lovable.app/api/cart?addService=auto-detailing&addProduct=foam-shampoo:2" | jq

curl β€” postMessage-equivalent payload (POST)

postMessage runs in-browser, not over HTTP β€” but the same envelope works against POST /api/cart:

curl -X POST "https://pops-personal-touch.lovable.app/api/cart" \
  -H "Content-Type: application/json" \
  -d '{
    "services": ["auto-detailing", "interior-detail"],
    "products": [{ "id": "foam-shampoo", "qty": 2 }, { "id": "tire-shine", "qty": 1 }]
  }'

Item IDs

Service and product IDs come from Manage Services and Manage Products. Current catalog:

Services

  • auto-detailing
  • carpet-shampooing
  • interior-detail
  • exterior-detail
  • clay-bar-treatment
  • headlight-restoration
  • engine-bay-cleaning

Products

  • foam-shampoo
  • wash-mitt
  • grit-guard
  • bug-tar-remover
  • ceramic-wax
  • carnauba-paste
  • clay-bar
  • polish-compound
  • detail-spray
  • tire-shine
  • wheel-cleaner
  • wheel-brush
  • interior-kit
  • leather-conditioner
  • carpet-shampoo
  • glass-cleaner
  • microfiber-set
  • wiper-blades
  • led-bulbs
  • jump-starter
  • floor-mats
  • air-freshener

🎟️ Coupon Landing Page API

GET /api/public/coupon renders a fully-styled, CORS-friendly coupon landing page you can link to, iframe, or share from any external site. The "Redeem" button hands off to /api/cart (the CartAPI documented above) using the same addService / addProduct URL params, so the landing page and the main store share one source of truth.

🎟️ Open Coupon Dashboard β†’ Build, save, edit, duplicate and share reusable coupons.

Query params

ParamExamplePurpose
codeSUMMER25Coupon code shown big & bold.
titleSummer Shine SaleHeadline.
subtitleEnds SundaySecondary line.
discount25Percent-off; drives the savings math.
discountLabel$25 OFFFree-form badge (overrides discount).
expires2026-12-31Displayed as "Expires …".
servicesauto-detailing,clay-bar-treatmentComma-separated service IDs.
productsfoam-shampoo:2,ceramic-wax:1Product IDs with optional :qty.
ctaRedeem NowButton label.
themedark / lightLanding page palette.
accent%23ef4444Hex accent color (URL-encode #).
logohttps://…/logo.pngOptional logo image URL.
returnUrlhttps://your.site/thanksOverride the Redeem destination.
formatjsonReturn JSON payload instead of HTML.

Testing URLs

  • https://pops-personal-touch.lovable.app/api/public/coupon?code=SUMMER25&title=Summer%20Shine%20Sale&subtitle=Ends%20Sunday&discount=25&expires=2026-12-31&services=auto-detailing,clay-bar-treatment&products=foam-shampoo:2,ceramic-wax:1&cta=Redeem%20Now
    Dark theme Β· 25% off Β· 2 services + 2 products
  • https://pops-personal-touch.lovable.app/api/public/coupon?code=FIRST10&title=Welcome%20Aboard&discount=10&theme=light&services=exterior-detail&products=detail-spray:1
    Light theme Β· 10% off Β· single service
  • https://pops-personal-touch.lovable.app/api/public/coupon?code=BOGO&title=Buy%20One%20Get%20One&discountLabel=BOGO&accent=%2322c55e&services=headlight-restoration,engine-bay-cleaning
    Custom green accent Β· custom badge label
  • https://pops-personal-touch.lovable.app/api/public/coupon?code=SUMMER25&discount=25&services=auto-detailing&products=foam-shampoo:2&format=json
    JSON payload (for custom external landing pages)
  • Launch URL

    https://pops-personal-touch.lovable.app/api/public/coupon?code=YOUR-CODE&title=Your%20Offer&discount=15&services=<id,id>&products=<id:qty,id:qty>

    Share this URL directly, drop it in email/SMS, or embed the endpoint in an <iframe>. CORS allows any origin.

    Notes

    • Cart is stored per-browser (localStorage). It is not synced across devices.
    • URL params are stripped after intake, so refreshing does not re-add items.
    • Modals and cart badges update live via the browser storage event.