Coupons और Reviews API
Coupon कैसे काम करता है
Coupon बस coupons table की एक row है जिसमें rules हैं। App सिर्फ code भेजती है; server rules को current cart से check करके नए totals लौटाता है।
Step 1: coupon_check()
यह function includes/cart.php में है (पूरी file Cart lesson में)। यहाँ steps mark करके दिखाया है:
<?php
// From includes/cart.php
function coupon_check($code, $subtotal)
{
$code = strtoupper(trim($code)); // "save10" works too
// 1. exists and active?
$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);
// 2. not expired?
if ($c['expires_at'] !== null && strtotime($c['expires_at']) < time()) { /* "This coupon has expired." */ }
// 3. minimum order reached?
if ($subtotal < (float) $c['min_order']) { /* "Add items worth ₹450 more to use SAVE10." */ }
// 4. calculate: percent (with a cap) or flat
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'];
}
// 5. never more than the subtotal
$discount = money(min($discount, $subtotal));
return array('ok' => true, 'message' => 'Coupon applied! You saved ' . rs($discount) . '.', 'discount' => $discount, 'code' => $code);
}Step 2: coupons/list.php और apply.php
list.php chips के लिए friendly title के साथ active coupons देता है। apply.php code को cart से check करके discount वाला cart लौटाता है। Order endpoint checkout पर coupon दोबारा check करता है — बीच में cart बदल सकता है।
<?php
// Active coupons shown on the checkout screen.
require_once __DIR__ . '/../../includes/helpers.php';
require_method('GET');
$rows = db()->query(
'SELECT code, type, value, min_order, max_discount, expires_at FROM coupons
WHERE is_active = 1 AND (expires_at IS NULL OR expires_at > NOW()) ORDER BY min_order'
)->fetchAll();
$out = array();
foreach ($rows as $c) {
$value = (float) $c['value'];
if ($c['type'] === 'percent') {
$title = (int) $value . '% off';
if ($c['max_discount'] !== null) {
$title .= ' up to ₹' . (int) $c['max_discount'];
}
} else {
$title = '₹' . (int) $value . ' off';
}
$out[] = array(
'code' => $c['code'],
'title' => $title,
'description' => 'On orders above ₹' . (int) $c['min_order'],
'min_order' => (float) $c['min_order'],
'expires_at' => $c['expires_at'],
);
}
ok($out);<?php
// POST {code} -> checks the coupon against the CURRENT cart and returns the new totals.
require_once __DIR__ . '/../../includes/helpers.php';
require_once __DIR__ . '/../../includes/cart.php';
require_method('POST');
$user = require_auth();
$code = in_str('code');
$plain = cart_summary($user['id']);
if ($plain['subtotal'] <= 0) {
fail('Your cart is empty.', 422);
}
$res = coupon_check($code, $plain['subtotal']);
if (!$res['ok']) {
fail($res['message'], 422);
}
ok(cart_summary($user['id'], $code), $res['message']);Coupon examples
| Code | type | value | min_order | max_discount | Cart ₹2,248 → |
|---|---|---|---|---|---|
| WELCOME50 | flat | 50 | 499 | — | − ₹50 |
| SAVE10 | percent | 10 | 999 | 300 | − ₹224.80 |
| DIWALI20 (admin ने बनाया) | percent | 20 | 500 | 250 | − ₹250 (capped) |
Reviews: सिर्फ असली खरीदार
Fake reviews भरोसा ख़त्म करते हैं। KartEasy review तभी allow करता है जब user का उस product वाला order delivered हो। दोबारा post करने पर पुराना review update होता है (UNIQUE product_id + user_id)।
Step 3: reviews/add.php और list.php
<?php
// POST {product_id, rating 1-5, comment?}
// Only customers who received the product can review it. Posting again updates the old review.
require_once __DIR__ . '/../../includes/helpers.php';
require_method('POST');
$user = require_auth();
$productId = in_int('product_id');
$rating = in_int('rating');
$comment = in_str('comment');
if ($rating < 1 || $rating > 5) {
fail('Rating must be between 1 and 5.', 422);
}
if (mb_strlen($comment) > 500) {
fail('Review can be at most 500 characters.', 422);
}
$bought = db()->prepare(
"SELECT 1 FROM orders o JOIN order_items i ON i.order_id = o.id
WHERE o.user_id = ? AND i.product_id = ? AND o.status = 'delivered' LIMIT 1"
);
$bought->execute(array($user['id'], $productId));
if (!$bought->fetch()) {
fail('You can review a product after it is delivered to you.', 403);
}
$pdo = db();
$pdo->beginTransaction();
$pdo->prepare(
'INSERT INTO reviews (product_id, user_id, rating, comment) VALUES (?, ?, ?, ?)
ON DUPLICATE KEY UPDATE rating = VALUES(rating), comment = VALUES(comment)'
)->execute(array($productId, $user['id'], $rating, $comment === '' ? null : $comment));
// Keep the cached average on the product row in sync.
$pdo->prepare(
'UPDATE products SET
rating_avg = (SELECT ROUND(AVG(rating), 1) FROM reviews WHERE product_id = ?),
rating_count = (SELECT COUNT(*) FROM reviews WHERE product_id = ?)
WHERE id = ?'
)->execute(array($productId, $productId, $productId));
$pdo->commit();
ok(null, 'Thanks for your review!');list.php reviews (हर page 20) और एक breakdown लौटाता है — कितने 5★, 4★ … — rating bars के लिए।
<?php
// GET api/reviews/list.php?product_id=7&page=1
require_once __DIR__ . '/../../includes/helpers.php';
require_method('GET');
$productId = q_int('product_id');
$page = max(1, q_int('page', 1));
$limit = 20;
$offset = ($page - 1) * $limit;
$stmt = db()->prepare(
"SELECT r.id, r.rating, r.comment, r.created_at, u.name FROM reviews r
JOIN users u ON u.id = r.user_id WHERE r.product_id = ?
ORDER BY r.id DESC LIMIT $limit OFFSET $offset"
);
$stmt->execute(array($productId));
$out = array();
foreach ($stmt->fetchAll() as $r) {
$out[] = array('id' => (int) $r['id'], 'name' => $r['name'], 'rating' => (int) $r['rating'],
'comment' => $r['comment'], 'date' => substr($r['created_at'], 0, 10));
}
// Star breakdown for the rating bars (5★ .. 1★)
$b = db()->prepare('SELECT rating, COUNT(*) n FROM reviews WHERE product_id = ? GROUP BY rating');
$b->execute(array($productId));
$bars = array(5 => 0, 4 => 0, 3 => 0, 2 => 0, 1 => 0);
foreach ($b->fetchAll() as $row) {
$bars[(int) $row['rating']] = (int) $row['n'];
}
ok(array('items' => $out, 'breakdown' => $bars));Product पर cached rating
हर request पर हर product card के लिए AVG(rating) निकालना slow होगा। इसलिए हम product row पर rating_avg और rating_count रखते हैं और review वाले transaction में ही update करते हैं। Product पढ़ना simple, तेज़ query रहता है।
Cache के बिना
- हर list: JOIN reviews
- 20 cards के लिए GROUP BY + AVG
- reviews बढ़ने पर slow
Cache के साथ (KartEasy)
- products.rating_avg = 4.5
- products.rating_count = 128
- सिर्फ review save होने पर update
