Wishlist & Address API
Wishlist: one toggle endpoint
Step 1: wishlist/toggle.php and list.php
rowCount() tells how many rows the DELETE removed. That single number tells us whether the product was in the wishlist. The UNIQUE (user_id, product_id) key prevents duplicates even if the user double-taps.
<?php
// POST {product_id} -> adds if missing, removes if present
require_once __DIR__ . '/../../includes/helpers.php';
require_method('POST');
$user = require_auth();
$productId = in_int('product_id');
$chk = db()->prepare('SELECT id FROM products WHERE id = ? AND is_active = 1');
$chk->execute(array($productId));
if (!$chk->fetch()) {
fail('Product not found.', 404);
}
$del = db()->prepare('DELETE FROM wishlist WHERE user_id = ? AND product_id = ?');
$del->execute(array($user['id'], $productId));
if ($del->rowCount() > 0) {
ok(array('product_id' => $productId, 'in_wishlist' => false), 'Removed from wishlist.');
}
db()->prepare('INSERT INTO wishlist (user_id, product_id) VALUES (?, ?)')->execute(array($user['id'], $productId));
ok(array('product_id' => $productId, 'in_wishlist' => true), 'Added to wishlist.');<?php
require_once __DIR__ . '/../../includes/helpers.php';
require_method('GET');
$user = require_auth();
$stmt = db()->prepare(
'SELECT p.* FROM wishlist w JOIN products p ON p.id = w.product_id
WHERE w.user_id = ? AND p.is_active = 1 ORDER BY w.id DESC'
);
$stmt->execute(array($user['id']));
$out = array();
foreach ($stmt->fetchAll() as $r) {
$p = product_out($r);
$p['in_wishlist'] = true;
$out[] = $p;
}
ok($out);Addresses: what checkout needs
Rules: Indian mobile (10 digits, starts with 6–9), pincode (6 digits, not starting with 0), and exactly one default address. The first address is always default.
Step 2: address/save.php
One endpoint for both "add" (no id) and "edit" (with id). Making an address default means first clearing the old default — two queries that must happen together, so they run inside a transaction.
<?php
// POST {id?, name, phone, line1, line2?, city, state, pincode, is_default?}
// Without id -> new address. With id -> update that address.
require_once __DIR__ . '/../../includes/helpers.php';
require_once __DIR__ . '/../../includes/address.php';
require_method('POST');
$user = require_auth();
require_fields(array(
'name' => 'Name', 'phone' => 'Phone', 'line1' => 'Address line 1',
'city' => 'City', 'state' => 'State', 'pincode' => 'Pincode',
));
$id = in_int('id');
$phone = in_str('phone');
$pincode = in_str('pincode');
if (!preg_match('/^[6-9][0-9]{9}$/', $phone)) {
fail('Please enter a valid 10-digit mobile number.', 422);
}
if (!preg_match('/^[1-9][0-9]{5}$/', $pincode)) {
fail('Please enter a valid 6-digit pincode.', 422);
}
$countStmt = db()->prepare('SELECT COUNT(*) FROM addresses WHERE user_id = ?');
$countStmt->execute(array($user['id']));
$isFirst = (int) $countStmt->fetchColumn() === 0;
$makeDefault = $isFirst || in_int('is_default') === 1 || in_str('is_default') === 'true';
$pdo = db();
$pdo->beginTransaction();
if ($makeDefault) {
$pdo->prepare('UPDATE addresses SET is_default = 0 WHERE user_id = ?')->execute(array($user['id']));
}
$line2 = in_str('line2');
$values = array(in_str('name'), $phone, in_str('line1'), $line2 === '' ? null : $line2, in_str('city'), in_str('state'), $pincode);
if ($id > 0) {
$own = $pdo->prepare('SELECT id FROM addresses WHERE id = ? AND user_id = ?');
$own->execute(array($id, $user['id']));
if (!$own->fetch()) {
$pdo->rollBack();
fail('Address not found.', 404);
}
$sql = 'UPDATE addresses SET name = ?, phone = ?, line1 = ?, line2 = ?, city = ?, state = ?, pincode = ?'
. ($makeDefault ? ', is_default = 1' : '') . ' WHERE id = ? AND user_id = ?';
$values[] = $id;
$values[] = $user['id'];
$pdo->prepare($sql)->execute($values);
} else {
$values[] = $makeDefault ? 1 : 0;
$values[] = $user['id'];
$pdo->prepare(
'INSERT INTO addresses (name, phone, line1, line2, city, state, pincode, is_default, user_id)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)'
)->execute($values);
$id = (int) $pdo->lastInsertId();
}
$pdo->commit();
$stmt = db()->prepare('SELECT * FROM addresses WHERE id = ?');
$stmt->execute(array($id));
ok(address_out($stmt->fetch()), 'Address saved.');<?php
function address_out($a)
{
return array(
'id' => (int) $a['id'],
'name' => $a['name'],
'phone' => $a['phone'],
'line1' => $a['line1'],
'line2' => $a['line2'],
'city' => $a['city'],
'state' => $a['state'],
'pincode' => $a['pincode'],
'is_default' => (int) $a['is_default'] === 1,
);
}
function address_text($a)
{
$parts = array($a['line1']);
if ($a['line2'] !== null && $a['line2'] !== '') {
$parts[] = $a['line2'];
}
$parts[] = $a['city'] . ', ' . $a['state'] . ' - ' . $a['pincode'];
return implode(', ', $parts);
}Ownership checks (very important)
The most common real-world API bug is called IDOR (Insecure Direct Object Reference): trusting an id sent by the app without checking who owns it.
<?php
// ❌ Dangerous: deletes ANY address whose id is sent
db()->prepare('DELETE FROM addresses WHERE id = ?')->execute(array($id));
// User 1 sends {"id": 57} → deletes user 2's address!
// ✅ Safe: the row must belong to the logged-in user
db()->prepare('DELETE FROM addresses WHERE id = ? AND user_id = ?')->execute(array($id, $user['id']));KartEasy does this for addresses, cart rows, wishlist, orders, order details and cancel. Search the backend for user_id = ? — you will find it in every customer endpoint.
Step 3: list.php and delete.php
When the default address is deleted, the newest remaining one becomes default, so checkout always has an address to pre-select.
<?php
require_once __DIR__ . '/../../includes/helpers.php';
require_once __DIR__ . '/../../includes/address.php';
require_method('GET');
$user = require_auth();
$stmt = db()->prepare('SELECT * FROM addresses WHERE user_id = ? ORDER BY is_default DESC, id DESC');
$stmt->execute(array($user['id']));
$out = array();
foreach ($stmt->fetchAll() as $a) {
$out[] = address_out($a);
}
ok($out);<?php
// POST {id}
require_once __DIR__ . '/../../includes/helpers.php';
require_method('POST');
$user = require_auth();
$id = in_int('id');
$stmt = db()->prepare('SELECT is_default FROM addresses WHERE id = ? AND user_id = ?');
$stmt->execute(array($id, $user['id']));
$row = $stmt->fetch();
if (!$row) {
fail('Address not found.', 404);
}
db()->prepare('DELETE FROM addresses WHERE id = ?')->execute(array($id));
// If the default was deleted, make the newest remaining address the default.
if ((int) $row['is_default'] === 1) {
db()->prepare('UPDATE addresses SET is_default = 1 WHERE user_id = ? ORDER BY id DESC LIMIT 1')
->execute(array($user['id']));
}
ok(null, 'Address deleted.');