> ## Documentation Index
> Fetch the complete documentation index at: https://twigz.arqqin.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Store Reviews

> Customer review and rating system for stores

## Overview

Store Reviews API provides comprehensive review and rating functionality including customer reviews, store responses, helpfulness voting, review flagging, and review management.

**Location:** `convex/stores/reviews.ts`

## Verified Purchase Logic

Reviews do **not** require a verified purchase. Any authenticated customer can leave a review on any store. The `isVerifiedPurchase` flag is set automatically:

* If `orderId` is provided: the flag is set to `true` (order must exist, belong to the customer, and be from the target store)
* If `orderId` is omitted: the system checks if the customer has any delivered order from the store. If yes, `isVerifiedPurchase` is `true`; otherwise `false`

Reviews without a verified purchase are still created and visible — they simply lack the verified badge.

***

## Add Review

Creates a new review for a store. Requires authentication. One review per order per customer (when `orderId` is provided).

<ParamField path="storeId" type="Id<'stores'>" required>
  Store ID to review
</ParamField>

<ParamField path="orderId" type="Id<'orders'>">
  Optional order ID to link the review to a specific order. If provided, automatically sets `isVerifiedPurchase` to true.
</ParamField>

<ParamField path="rating" type="number" required>
  Rating from 1-5 stars
</ParamField>

<ParamField path="title" type="string">
  Review title (at least one of `title` or `comment` is required)
</ParamField>

<ParamField path="comment" type="string">
  Review comment (at least one of `title` or `comment` is required)
</ParamField>

<ParamField path="photoIds" type="Id<'_storage'>[]">
  Array of storage IDs for review photos
</ParamField>

<CodeGroup>
  ```typescript TypeScript theme={null}
  const review = await convex.mutation(api.stores.reviews.addReview, {
    storeId: "j123456789",
    orderId: "o123456789", // optional
    rating: 5,
    title: "Excellent food!",
    comment: "The pizza was amazing and delivery was fast. Highly recommended!",
    photoIds: ["s123456789"]
  });
  ```
</CodeGroup>

<ResponseExample>
  ```json Response theme={null}
  {
    "reviewId": "r123456789",
    "message": "Review added successfully"
  }
  ```
</ResponseExample>

***

## Update Review

Updates an existing review. Only the review author can update their own review.

<ParamField path="reviewId" type="Id<'storeReviews'>" required>
  Review ID to update
</ParamField>

<ParamField path="rating" type="number">
  Updated rating (1-5)
</ParamField>

<ParamField path="title" type="string">
  Updated title
</ParamField>

<ParamField path="comment" type="string">
  Updated comment
</ParamField>

<ParamField path="photoIds" type="Id<'_storage'>[]">
  Updated photo IDs
</ParamField>

<CodeGroup>
  ```typescript TypeScript theme={null}
  await convex.mutation(api.stores.reviews.updateReview, {
    reviewId: "r123456789",
    rating: 4,
    comment: "Updated: Still great food, but service was slower this time."
  });
  ```
</CodeGroup>

***

## Delete Review

Deletes a review and all associated data (helpful votes, flags). Only the review author can delete their own review.

<ParamField path="reviewId" type="Id<'storeReviews'>" required>
  Review ID to delete
</ParamField>

<CodeGroup>
  ```typescript TypeScript theme={null}
  await convex.mutation(api.stores.reviews.deleteReview, {
    reviewId: "r123456789"
  });
  ```
</CodeGroup>

***

## Get Store Reviews

Lists reviews for a store with pagination, sorting, and filtering. Only visible reviews are returned. Does not require authentication, but authenticated users see their own helpfulness votes.

<ParamField path="storeId" type="Id<'stores'>" required>
  Store ID
</ParamField>

<ParamField path="paginationOpts" type="PaginationOptions" required>
  Pagination options
</ParamField>

<ParamField path="sortBy" type="string">
  Sort order: `newest`, `oldest`, `highest_rating`, `lowest_rating`, `most_helpful`
</ParamField>

<ParamField path="ratingFilter" type="number">
  Filter by specific rating (1-5)
</ParamField>

<ParamField path="verifiedOnly" type="boolean">
  Show only verified purchase reviews
</ParamField>

<CodeGroup>
  ```typescript TypeScript theme={null}
  const reviews = await convex.query(api.stores.reviews.getStoreReviews, {
    storeId: "j123456789",
    paginationOpts: { numItems: 10, cursor: null },
    sortBy: "newest",
    verifiedOnly: true
  });
  ```
</CodeGroup>

<ResponseExample>
  ```json Response theme={null}
  {
    "page": [
      {
        "_id": "r123456789",
        "_creationTime": 1700000000000,
        "rating": 5,
        "title": "Excellent food!",
        "comment": "The pizza was amazing.",
        "photoUrls": ["https://..."],
        "isVerifiedPurchase": true,
        "helpfulCount": 3,
        "notHelpfulCount": 0,
        "storeResponse": {
          "comment": "Thank you!",
          "respondedAt": 1700001000000
        },
        "customer": {
          "_id": "c123456789",
          "name": "Jane"
        },
        "userVote": true
      }
    ],
    "isDone": false,
    "continueCursor": "cursor123"
  }
  ```
</ResponseExample>

***

## Get Store Review Stats

Returns denormalized review statistics for a store. Fast query — reads pre-computed stats from the store document.

<ParamField path="storeId" type="Id<'stores'>" required>
  Store ID
</ParamField>

<CodeGroup>
  ```typescript TypeScript theme={null}
  const stats = await convex.query(api.stores.reviews.getStoreReviewStats, {
    storeId: "j123456789"
  });
  ```
</CodeGroup>

***

## Get Review

Fetches a single review by ID with full details including customer info, store info, photo URLs, and current user's permissions and vote.

<ParamField path="reviewId" type="Id<'storeReviews'>" required>
  Review ID
</ParamField>

<CodeGroup>
  ```typescript TypeScript theme={null}
  const review = await convex.query(api.stores.reviews.getReview, {
    reviewId: "r123456789"
  });
  // response includes: canEdit, canDelete, userVote
  ```
</CodeGroup>

***

## Get Customer Reviews

Lists all reviews written by the authenticated customer with pagination. Includes store info and store responses.

<ParamField path="paginationOpts" type="PaginationOptions" required>
  Pagination options
</ParamField>

<CodeGroup>
  ```typescript TypeScript theme={null}
  const myReviews = await convex.query(api.stores.reviews.getCustomerReviews, {
    paginationOpts: { numItems: 10, cursor: null }
  });
  ```
</CodeGroup>

***

## Add Store Response

Allows a store owner to respond to a review. One response per review.

<ParamField path="reviewId" type="Id<'storeReviews'>" required>
  Review ID to respond to
</ParamField>

<ParamField path="comment" type="string" required>
  Store response comment
</ParamField>

<CodeGroup>
  ```typescript TypeScript theme={null}
  await convex.mutation(api.stores.reviews.addStoreResponse, {
    reviewId: "r123456789",
    comment: "Thank you for your wonderful review! We're glad you enjoyed our pizza."
  });
  ```
</CodeGroup>

***

## Update Store Response

Allows a store owner to edit their existing response.

<ParamField path="reviewId" type="Id<'storeReviews'>" required>
  Review ID with the response to update
</ParamField>

<ParamField path="comment" type="string" required>
  Updated response comment
</ParamField>

<CodeGroup>
  ```typescript TypeScript theme={null}
  await convex.mutation(api.stores.reviews.updateStoreResponse, {
    reviewId: "r123456789",
    comment: "Updated: Thank you! We've improved our delivery times based on your feedback."
  });
  ```
</CodeGroup>

***

## Vote on Review

Vote on whether a review is helpful or not. Customers cannot vote on their own reviews. One vote per customer per review (can be changed).

<ParamField path="reviewId" type="Id<'storeReviews'>" required>
  Review ID to vote on
</ParamField>

<ParamField path="isHelpful" type="boolean" required>
  `true` for helpful, `false` for not helpful
</ParamField>

<CodeGroup>
  ```typescript TypeScript theme={null}
  await convex.mutation(api.stores.reviews.voteOnReview, {
    reviewId: "r123456789",
    isHelpful: true
  });
  ```
</CodeGroup>

***

## Flag Review

Report a review for moderation. One flag per customer per review. Creates a pending flag for admin review and sends an admin notification (high priority for offensive/fake reasons).

<ParamField path="reviewId" type="Id<'storeReviews'>" required>
  Review ID to flag
</ParamField>

<ParamField path="reason" type="string" required>
  Reason for flagging: `spam`, `inappropriate`, `fake`, `offensive`, `irrelevant`, `other`
</ParamField>

<ParamField path="description" type="string">
  Additional details about the flag
</ParamField>

<CodeGroup>
  ```typescript TypeScript theme={null}
  await convex.mutation(api.stores.reviews.flagReview, {
    reviewId: "r123456789",
    reason: "fake",
    description: "This customer never ordered from our store"
  });
  ```
</CodeGroup>

<Note>
  Reviews include photo support, verified purchase indicators, helpfulness voting, store owner responses, and a full flagging/moderation pipeline.
</Note>
