Free tutorials & notes in Hindi & English · Clean code examples 🧰 मुफ़्त PDF और फ़ोटो टूल्स →
🛠️ PHP + MySQL REST API  ·  Lesson 10

Coupons और Reviews API

Coupon कैसे काम करता है

Coupon बस coupons table की एक row है जिसमें rules हैं। App सिर्फ code भेजती है; server rules को current cart से check करके नए totals लौटाता है।

9:41▮▮ 85%←Checkout⋮🏷️ Apply couponCoupon codeWELCOME50 · ₹50 offSAVE10 applied · you save₹224.80Coupon (SAVE10)− ₹224.80Total amount₹2,023.20Place order
Checkout screen पर coupons

Step 1: coupon_check()

यह function includes/cart.php में है (पूरी file Cart lesson में)। यहाँ steps mark करके दिखाया है:

PHP – includes/cart.php (छोटा किया, comments जोड़े)
<?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);
}
Exists और active?"Invalid coupon code."Expire तो नहीं?"This coupon has expired."Minimum order?"Add items worth ₹450 more…"Calculatemax cap के साथ percent, या flatSubtotal पर captotal कभी negative नहीं
पाँच checks, एक discount

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 – backend/api/coupons/list.php
<?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 – backend/api/coupons/apply.php
<?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

Codetypevaluemin_ordermax_discountCart ₹2,248 →
WELCOME50flat50499—− ₹50
SAVE10percent10999300− ₹224.80
DIWALI20 (admin ने बनाया)percent20500250− ₹250 (capped)

Reviews: सिर्फ असली खरीदार

Fake reviews भरोसा ख़त्म करते हैं। KartEasy review तभी allow करता है जब user का उस product वाला order delivered हो। दोबारा post करने पर पुराना review update होता है (UNIQUE product_id + user_id)।

9:41▮▮ 85%←Order details⋮Steel Water Bottle 1LQty 1 × ₹549 ★ Rate✔ Placed ✔ Confirmed ✔ Shipped ✔Delivered9:41▮▮ 85%←Order details⋮Steel Water Bottle 1LQty 1 × ₹549Rate this product★★★★★ Keeps water cold…CancelSubmit
"Rate" button सिर्फ delivered orders पर दिखता है

Step 3: reviews/add.php और list.php

PHP – backend/api/reviews/add.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 – backend/api/reviews/list.php
<?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
थोड़ा ज़्यादा लिखें, बहुत तेज़ पढ़ें

असली responses

Available coupons
GET /karteasy/api/coupons/list.php HTTP 200 { "success": true, "message": "OK", "data": [ { "code": "WELCOME50", "title": "₹50 off", "description": "On orders above ₹499", "min_order": 499, "expires_at": "2027-12-31 23:59:59" }, { "code": "SAVE10", "title": "10% off up to ₹300", "description": "On orders above ₹999", "min_order": 999, "expires_at": "2027-12-31 23:59:59" } ] }
SAVE10 apply (lowercase भी चलता है)
POST /karteasy/api/coupons/apply.php Authorization: Bearer 3f9c…e1a7 {"code": "save10"} HTTP 200 { "success": true, "message": "Coupon applied! You saved ₹224.80.", "data": { "items": [ { "product_id": 13, "name": "Learn Kotlin in 30 Days", "image": "https://yourdomain.com/karteasy/uploads/products/p13.png", "price": 349, "mrp": 499, "qty": 1, "stock": 70, "line_total": 349, "available": true }, "… 1 more" ], "item_count": 2, "mrp_total": 5498, "subtotal": 2248, "you_save": 3474.8, "discount": 224.8, "coupon_code": "SAVE10", "coupon_message": "Coupon applied! You saved ₹224.80.", "delivery_fee": 0, "total": 2023.2 } }
Minimum order नहीं पहुँचा
POST /karteasy/api/coupons/apply.php Authorization: Bearer 3f9c…e1a7 {"code": "SAVE10"} HTTP 422 { "success": false, "message": "Add items worth ₹450 more to use SAVE10.", "data": null }
Unknown code
POST /karteasy/api/coupons/apply.php Authorization: Bearer 3f9c…e1a7 {"code": "FREE100"} HTTP 422 { "success": false, "message": "Invalid coupon code.", "data": null }
Delivery से पहले review
POST /karteasy/api/reviews/add.php Authorization: Bearer 3f9c…e1a7 {"product_id": 7, "rating": 5, "comment": "Great watch"} HTTP 403 { "success": false, "message": "You can review a product after it is delivered to you.", "data": null }
Delivery के बाद review
POST /karteasy/api/reviews/add.php Authorization: Bearer 3f9c…e1a7 {"product_id": 12, "rating": 5, "comment": "Keeps water cold all day. Worth it!"} HTTP 200 { "success": true, "message": "Thanks for your review!", "data": null }
गलत rating value
POST /karteasy/api/reviews/add.php Authorization: Bearer 3f9c…e1a7 {"product_id": 12, "rating": 7, "comment": ""} HTTP 422 { "success": false, "message": "Rating must be between 1 and 5.", "data": null }
Breakdown के साथ reviews
GET /karteasy/api/reviews/list.php?product_id=12 HTTP 200 { "success": true, "message": "OK", "data": { "items": [ { "id": 3, "name": "Riya Sharma", "rating": 5, "comment": "Keeps water cold all day. Worth it!", "date": "2026-10-19" } ], "breakdown": { "5": 1, "4": 0, "3": 0, "2": 0, "1": 0 } } }

अक्सर पूछे जाने वाले प्रश्न (FAQ)

हर user के लिए सिर्फ एक बार चलने वाला coupon कैसे बनाएँ?
UNIQUE key के साथ coupon_uses table (coupon_id, user_id) जोड़ें, place-order transaction में एक row insert करें, और coupon_check() में उस user की row पहले से हो तो code reject करें।
पहले review के बाद rating numbers अलग क्यों दिखते हैं?
Sample products में demo rating counts हैं। असली review save होते ही rating_avg और rating_count reviews table से दोबारा calculate होते हैं, इसलिए उसके बाद असली numbers दिखते हैं।
← E-commerce App Tutorial
🔗

Share this topic with a friend

यह topic किसी दोस्त को भेजें

Found it useful? Send it to a classmate learning the same thing.

अच्छा लगा? जो दोस्त यही सीख रहा है, उसे भेज दीजिए।

अगला topicAdmin Panel (Products और Orders) आपकी प्रगति: 10 / 28 (36%) · Lesson 11 / 28 आगे बढ़ें →

यह lesson कैसा लगा?

आपकी राय से हम content और बेहतर बनाते हैं।

अपना review और सुझाव लिखें (वैकल्पिक)

Review approve होने के बाद आपका नाम और शहर इसके साथ public दिख सकते हैं। Email या phone न लिखें।

▶ CodingEasily on YouTube

Video से सीखें – आसान Hindi में

चुनी हुई video: RDBMS भाग 3 – Class XI Commerce IT

▶ Channel subscribe करें