Cart API
Cart के rules
- 1सिर्फ logged-in users का cart होता है (
require_auth()) - 2हर user के हर product की एक row — दोबारा "Add" दबाने पर quantity बढ़ती है
- 3Quantity 1 से 10 के बीच, और stock से कभी ज़्यादा नहीं
- 4Prices और totals हमेशा server पर calculate होते हैं, app कभी नहीं भेजती
- 5Subtotal ₹499 या ज़्यादा हो तो free delivery, वरना ₹40 (config.php में)
- 6Hidden या out-of-stock items दिखते रहते हैं पर
available: falsemark होते हैं और गिने नहीं जाते
Step 1: includes/cart.php
तीन endpoints (cart, coupons, orders) को वही calculations चाहिए, इसलिए वे एक shared file में हैं। cart_rows() cart को live product data से जोड़ता है; cart_summary() response बनाता है; coupon_check() Coupons lesson में समझाया गया है।
<?php
// Cart and coupon calculations, shared by cart/list, coupons/apply and orders/place.
// The server always calculates prices itself: never trust a total sent by the app.
/** Cart rows of a user with live product price and stock. */
function cart_rows($userId)
{
$stmt = db()->prepare(
'SELECT c.product_id, c.qty, p.name, p.image, p.price, p.mrp, p.stock, p.is_active
FROM cart_items c JOIN products p ON p.id = c.product_id
WHERE c.user_id = ? ORDER BY c.id DESC'
);
$stmt->execute(array($userId));
return $stmt->fetchAll();
}
/**
* Check a coupon against a subtotal.
* Returns array('ok' => bool, 'message' => string, 'discount' => float).
*/
function coupon_check($code, $subtotal)
{
$code = strtoupper(trim($code));
if ($code === '') {
return array('ok' => false, 'message' => 'Enter a coupon code.', 'discount' => 0);
}
$stmt = db()->prepare('SELECT * FROM coupons WHERE code = ? AND is_active = 1');
$stmt->execute(array($code));
$c = $stmt->fetch();
if (!$c) {
return array('ok' => false, 'message' => 'Invalid coupon code.', 'discount' => 0);
}
if ($c['expires_at'] !== null && strtotime($c['expires_at']) < time()) {
return array('ok' => false, 'message' => 'This coupon has expired.', 'discount' => 0);
}
if ($subtotal < (float) $c['min_order']) {
return array('ok' => false,
'message' => 'Add items worth ' . rs($c['min_order'] - $subtotal) . ' more to use ' . $code . '.',
'discount' => 0);
}
if ($c['type'] === 'percent') {
$discount = $subtotal * (float) $c['value'] / 100;
if ($c['max_discount'] !== null) {
$discount = min($discount, (float) $c['max_discount']);
}
} else {
$discount = (float) $c['value'];
}
$discount = money(min($discount, $subtotal));
return array('ok' => true, 'message' => 'Coupon applied! You saved ' . rs($discount) . '.', 'discount' => $discount, 'code' => $code);
}
/** Full cart with totals. Pass a coupon code to include its discount. */
function cart_summary($userId, $couponCode = '')
{
$items = array();
$subtotal = 0;
$mrpTotal = 0;
$count = 0;
foreach (cart_rows($userId) as $r) {
$available = ((int) $r['is_active'] === 1) && ((int) $r['stock'] >= (int) $r['qty']);
$line = (float) $r['price'] * (int) $r['qty'];
if ($available) {
$subtotal += $line;
$mrpTotal += (float) $r['mrp'] * (int) $r['qty'];
$count += (int) $r['qty'];
}
$items[] = array(
'product_id' => (int) $r['product_id'],
'name' => $r['name'],
'image' => img_url($r['image']),
'price' => (float) $r['price'],
'mrp' => (float) $r['mrp'],
'qty' => (int) $r['qty'],
'stock' => (int) $r['stock'],
'line_total' => money($line),
'available' => $available,
);
}
$discount = 0;
$couponMsg = null;
$appliedCode = null;
if ($couponCode !== '' && $subtotal > 0) {
$res = coupon_check($couponCode, $subtotal);
$couponMsg = $res['message'];
if ($res['ok']) {
$discount = $res['discount'];
$appliedCode = $res['code'];
}
}
$delivery = ($subtotal == 0 || $subtotal >= FREE_DELIVERY_ABOVE) ? 0 : DELIVERY_FEE;
return array(
'items' => $items,
'item_count' => $count,
'mrp_total' => money($mrpTotal),
'subtotal' => money($subtotal),
'you_save' => money($mrpTotal - $subtotal + $discount),
'discount' => money($discount),
'coupon_code' => $appliedCode,
'coupon_message' => $couponMsg,
'delivery_fee' => money($delivery),
'total' => money($subtotal - $discount + $delivery),
);
}Step 2: cart/add.php
Add पहले stock और cart में पहले से रखी quantity पढ़ता है, limits check करता है, फिर एक "upsert" query चलाता है:
-- First tap on "Add": no row yet → INSERT -- Second tap: row exists (UNIQUE user_id + product_id) → UPDATE qty instead INSERT INTO cart_items (user_id, product_id, qty) VALUES (1, 7, 2) ON DUPLICATE KEY UPDATE qty = VALUES(qty);
<?php
// POST {product_id, qty} -> adds qty to the cart (creates the row if needed)
require_once __DIR__ . '/../../includes/helpers.php';
require_once __DIR__ . '/../../includes/cart.php';
require_method('POST');
$user = require_auth();
$productId = in_int('product_id');
$qty = max(1, in_int('qty', 1));
$stmt = db()->prepare('SELECT stock FROM products WHERE id = ? AND is_active = 1');
$stmt->execute(array($productId));
$stock = $stmt->fetchColumn();
if ($stock === false) {
fail('Product not found.', 404);
}
$cur = db()->prepare('SELECT qty FROM cart_items WHERE user_id = ? AND product_id = ?');
$cur->execute(array($user['id'], $productId));
$newQty = (int) $cur->fetchColumn() + $qty;
if ($newQty > (int) $stock) {
fail('Only ' . (int) $stock . ' left in stock.', 409);
}
if ($newQty > 10) {
fail('You can buy at most 10 of one product.', 409);
}
// One query for both cases thanks to the UNIQUE(user_id, product_id) key.
db()->prepare(
'INSERT INTO cart_items (user_id, product_id, qty) VALUES (?, ?, ?)
ON DUPLICATE KEY UPDATE qty = VALUES(qty)'
)->execute(array($user['id'], $productId, $newQty));
ok(cart_summary($user['id']), 'Added to cart.');Step 3: update.php और remove.php
update.php exact quantity set करता है (− / + buttons के लिए)। qty: 0 भेजने पर item हट जाता है। remove.php row delete करता है (swipe to delete)।
<?php
// POST {product_id, qty} -> sets the exact qty (qty 0 removes the item)
require_once __DIR__ . '/../../includes/helpers.php';
require_once __DIR__ . '/../../includes/cart.php';
require_method('POST');
$user = require_auth();
$productId = in_int('product_id');
$qty = in_int('qty');
if ($qty <= 0) {
db()->prepare('DELETE FROM cart_items WHERE user_id = ? AND product_id = ?')->execute(array($user['id'], $productId));
ok(cart_summary($user['id']), 'Item removed.');
}
$stmt = db()->prepare('SELECT stock FROM products WHERE id = ? AND is_active = 1');
$stmt->execute(array($productId));
$stock = $stmt->fetchColumn();
if ($stock === false) {
fail('Product not found.', 404);
}
if ($qty > (int) $stock) {
fail('Only ' . (int) $stock . ' left in stock.', 409);
}
if ($qty > 10) {
fail('You can buy at most 10 of one product.', 409);
}
$upd = db()->prepare('UPDATE cart_items SET qty = ? WHERE user_id = ? AND product_id = ?');
$upd->execute(array($qty, $user['id'], $productId));
if ($upd->rowCount() === 0) {
// Not in cart yet (or same qty) -> make sure the row exists
db()->prepare(
'INSERT INTO cart_items (user_id, product_id, qty) VALUES (?, ?, ?)
ON DUPLICATE KEY UPDATE qty = VALUES(qty)'
)->execute(array($user['id'], $productId, $qty));
}
ok(cart_summary($user['id']), 'Cart updated.');<?php
// POST {product_id}
require_once __DIR__ . '/../../includes/helpers.php';
require_once __DIR__ . '/../../includes/cart.php';
require_method('POST');
$user = require_auth();
db()->prepare('DELETE FROM cart_items WHERE user_id = ? AND product_id = ?')
->execute(array($user['id'], in_int('product_id')));
ok(cart_summary($user['id']), 'Item removed.');Step 4: cart/list.php
सिर्फ तीन lines, क्योंकि काम cart_summary() करता है। Optional ?coupon= से checkout coupon लगाकर cart reload कर सकता है।
<?php
// GET api/cart/list.php?coupon=SAVE10 (coupon is optional)
require_once __DIR__ . '/../../includes/helpers.php';
require_once __DIR__ . '/../../includes/cart.php';
require_method('GET');
$user = require_auth();
ok(cart_summary($user['id'], q_str('coupon')));Totals कैसे calculate होते हैं
| Field | Formula | Example |
|---|---|---|
| mrp_total | Σ mrp × qty | 4999 + 499 × 2 = 5,997 |
| subtotal | Σ price × qty | 1899 + 349 × 2 = 2,597 |
| discount | coupon discount (बिना coupon 0) | 0 |
| delivery_fee | subtotal ≥ 499 तो 0, वरना 40 | 0 |
| total | subtotal − discount + delivery_fee | 2,597 |
| you_save | mrp_total − subtotal + discount | 3,400 |
कोई भी tool से API call करके "price": 1 भेज सकता है। इसीलिए add.php सिर्फ product_id और qty लेता है, और हर रुपया database से calculate होता है।
