Skip to content

Repository files navigation

LT Land Valuation

This is a Next.js app for selling paid Lithuanian land/forest valuation requests. Users create an account, submit cadastral/location/notes/photos, pay through Stripe Checkout, and then an expert completes the valuation from Telegram.

The current production-like workflow is human-in-the-loop:

  1. User submits a valuation request.
  2. User pays via Stripe Checkout.
  3. Stripe sends checkout.session.completed to the app.
  4. The app marks the request as PROCESSING.
  5. The app sends the full request and uploaded photos to a configured Telegram chat.
  6. An expert replies to the original Telegram request message with a structured template.
  7. The Telegram webhook parses the reply, creates the report, and marks the request COMPLETED.
  8. The client page polls and updates automatically when the report is ready.

Tech Stack

  • Next.js 15 App Router
  • React 18
  • TypeScript
  • Tailwind CSS
  • Prisma ORM
  • PostgreSQL, currently Neon in the existing setup
  • Stripe Checkout for one-time payments
  • Telegram Bot API for expert review and sell-request notifications
  • Vitest for tests

Important Concepts

Valuation Request Statuses

ValuationRequest.status uses the existing enum:

  • PENDING_PAYMENT: request exists, user has not completed Stripe checkout.
  • PROCESSING: payment succeeded and request has been sent to Telegram, or is waiting for Telegram completion.
  • COMPLETED: a valid expert reply was parsed and a ValuationReport exists.
  • FAILED: payment webhook processing failed, usually Telegram delivery/config failure.

Human Evaluation Reply Format

The expert must reply directly to the original bot request message with:

VALUE_EUR:
LAND_AREA_SQM:
FOREST_SHARE_PCT:
SUMMARY:
DETAILS:

Example:

VALUE_EUR:19000
LAND_AREA_SQM:10000
FOREST_SHARE_PCT:50%
SUMMARY:Orientacinė sklypo vertė yra 19 000 EUR.
DETAILS:Vertinimas atliktas pagal pateiktus duomenis, vietovės aprašymą ir nuotraukas.

FOREST_SHARE_PCT may include %. The app accepts 50 and 50%.

The bot replies in Telegram after processing:

  • success: confirms the request ID and saved values.
  • invalid format: explains which required fields are wrong.
  • not a reply: tells the expert to reply to the original request message.
  • unmatched reply: tells the expert the reply did not match an active request.

Environment Variables

Create .env from .env.example.

Required:

DATABASE_PROVIDER=postgresql
DATABASE_URL="postgresql://..."

STRIPE_SECRET_KEY=sk_test_...
STRIPE_WEBHOOK_SECRET=whsec_...
STRIPE_PRICE_ID=price_...

NEXT_PUBLIC_BASE_URL=https://your-public-url

TELEGRAM_BOT_TOKEN=123456789:bot-token
TELEGRAM_CHAT_ID=123456789
TELEGRAM_WEBHOOK_SECRET=random-secret-string

Optional legacy/debug values:

OPENROUTER_API_KEY=sk-or-v1-...
OPENROUTER_MODEL=openai/gpt-4.1-mini
OPENROUTER_VISION_MODEL=openai/gpt-4.1-mini
OPENROUTER_PRICING_MODEL=openai/gpt-4.1-mini
DEBUG_VALUATION=true
DEBUG_TELEGRAM=true

OpenRouter code still exists in lib/valuation.ts for tests/legacy fallback, but paid valuation requests now go to Telegram, not OpenRouter.

NEXT_PUBLIC_BASE_URL

This must be a public HTTPS URL when Telegram needs to call the app.

For the current local setup, this is usually an ngrok URL, for example:

NEXT_PUBLIC_BASE_URL=https://daunting-plasma-gravity.ngrok-free.dev

Avoid a trailing slash.

Good:

NEXT_PUBLIC_BASE_URL=https://example.ngrok-free.dev

Avoid:

NEXT_PUBLIC_BASE_URL=https://example.ngrok-free.dev/

Local Development Setup

Install dependencies:

npm install

Generate Prisma client:

npm run prisma:generate

Run the app:

npm run dev

The site runs at:

http://localhost:3000

Keep this terminal running.

Stripe Local Webhook

In a separate terminal, run:

stripe listen --forward-to localhost:3000/api/stripe/webhook

Stripe CLI prints a webhook signing secret like:

whsec_...

Put that value in .env:

STRIPE_WEBHOOK_SECRET=whsec_...

Then restart npm run dev so Next reads the updated env.

The app only handles:

checkout.session.completed

Telegram Local Webhook With Ngrok

Telegram cannot call localhost, so expose the local app with ngrok.

Example:

ngrok http 3000

Copy the HTTPS forwarding URL and set it in .env:

NEXT_PUBLIC_BASE_URL=https://your-current-ngrok-url.ngrok-free.app

Restart npm run dev.

Then register the Telegram webhook:

$token = "YOUR_TELEGRAM_BOT_TOKEN"
$secret = "YOUR_TELEGRAM_WEBHOOK_SECRET"
$baseUrl = "https://your-current-ngrok-url.ngrok-free.app"

Invoke-RestMethod "https://api.telegram.org/bot$token/setWebhook?url=$baseUrl/api/telegram/webhook&secret_token=$secret"

Check what Telegram currently has saved:

Invoke-RestMethod "https://api.telegram.org/bot$token/getWebhookInfo"

If you use free/random ngrok, the URL changes often. Every time it changes:

  1. Update NEXT_PUBLIC_BASE_URL in .env.
  2. Restart npm run dev.
  3. Run setWebhook again with the new URL.
  4. Verify with getWebhookInfo.

If Telegram says:

404 Not Found

it is probably pointing at an old/wrong URL.

If Telegram says:

502 Bad Gateway

ngrok is reachable but the local Next server is probably not running.

Best long-term fix: use a reserved/static ngrok domain or deploy to a real HTTPS domain.

Database

The app uses Prisma with PostgreSQL.

Schema file:

prisma/schema.prisma

Current important models:

  • User
  • Session
  • ValuationRequest
  • ValuationReport

Telegram tracking fields live on ValuationRequest:

telegramReviewChatId
telegramReviewMessageId
telegramReviewSentAt
telegramReviewCompletedAt

Existing Neon DB Note

The current Neon DB was not fully created from Prisma migration history. Because of that, prisma migrate dev may fail with a shadow database error like:

P3006
The underlying table for model `ValuationRequest` does not exist.

For the Telegram tracking migration, the working command was:

npx prisma db execute --schema prisma/schema.prisma --file prisma/migrations/20260422120000_add_telegram_review_tracking/migration.sql
npm run prisma:generate

Verify the columns:

npx prisma db pull --print | Select-String "telegramReview"

If prisma:generate fails on Windows with EPERM rename query_engine..., some Node process is locking Prisma's DLL. Stop Node processes and rerun:

Get-Process node -ErrorAction SilentlyContinue | Stop-Process
npm run prisma:generate

For a clean future production DB that does have migration history, use:

npx prisma migrate deploy

End-to-End Local Test Flow

Run these three things:

Terminal 1:

npm run dev

Terminal 2:

stripe listen --forward-to localhost:3000/api/stripe/webhook

Terminal 3:

ngrok http 3000

Then:

  1. Update .env with ngrok NEXT_PUBLIC_BASE_URL.
  2. Restart npm run dev.
  3. Register Telegram webhook using setWebhook.
  4. Open http://localhost:3000.
  5. Register or log in.
  6. Create a valuation request.
  7. Pay through Stripe Checkout test mode.
  8. Confirm the bot posts the request to Telegram.
  9. Reply to the original Telegram request message with the required template.
  10. Confirm the bot replies Evaluation saved.
  11. Return to the app; checkout success or valuation detail page should update to COMPLETED.

Project Structure

app/
  api/
    auth/                 Login/register/logout API routes
    stripe/webhook/       Stripe payment webhook
    telegram/webhook/     Telegram expert reply webhook
    valuation/            Valuation CRUD, lookup, sell-request routes
  auth/                   Login/register pages
  checkout/               Stripe success/cancel pages
  dashboard/              User dashboard
  valuation/              New valuation and detail pages

lib/
  auth.ts                 Session and auth helpers
  i18n.ts                 Lithuanian/English/Russian UI strings
  prisma.ts               Prisma singleton
  stripe.ts               Stripe client
  telegram.ts             Telegram Bot API helpers
  telegram-evaluation.ts  Telegram request builder and reply parser
  valuation.ts            Legacy deterministic/OpenRouter valuation helpers

prisma/
  schema.prisma           Database schema
  migrations/             SQL migrations

public/uploads/           Local uploaded valuation photos, gitignored

Key Files For Future Work

Stripe payment completion:

app/api/stripe/webhook/route.ts

Telegram reply processing:

app/api/telegram/webhook/route.ts

Telegram message format and parser:

lib/telegram-evaluation.ts

Telegram low-level API calls:

lib/telegram.ts

Valuation page polling:

app/valuation/[id]/ValuationClient.tsx
app/checkout/success/CheckoutSuccessClient.tsx

Sell-this-land Telegram notification:

app/api/valuation/[id]/sell-request/route.ts

Testing

Run all tests:

npm test

Run production build/type checks:

npm run build

Current important test files:

lib/telegram-evaluation.test.ts
app/api/telegram/webhook/route.test.ts
app/api/stripe/webhook/route.test.ts
app/api/valuation/[id]/sell-request/route.test.ts
app/api/valuation/create/route.test.ts
lib/valuation.test.ts

Troubleshooting

Bot sends request, but expert reply does nothing

Check Telegram webhook:

Invoke-RestMethod "https://api.telegram.org/bot$token/getWebhookInfo"

Common causes:

  • Webhook URL points to an old ngrok URL.
  • npm run dev is not running.
  • ngrok is not running.
  • Expert did not reply to the original bot request message.
  • TELEGRAM_CHAT_ID does not match the chat where replies happen.
  • TELEGRAM_WEBHOOK_SECRET was changed locally but webhook was not re-registered.

Bot says reply does not match active request

The expert likely replied to a photo message or copied text into a new message. They must reply directly to the original request message that contains the template.

Stripe payment succeeds but Telegram gets nothing

Check:

  • stripe listen --forward-to localhost:3000/api/stripe/webhook is running.
  • STRIPE_WEBHOOK_SECRET in .env matches the current Stripe CLI secret.
  • TELEGRAM_BOT_TOKEN and TELEGRAM_CHAT_ID are valid.
  • App terminal has no Stripe webhook errors.

502 Bad Gateway in Telegram webhook info

ngrok can reach your machine, but Next is probably not running. Start:

npm run dev

404 Not Found in Telegram webhook info

Telegram is pointing at the wrong URL. Register webhook again with the current ngrok URL.

Security Notes

  • User passwords are hashed with bcrypt.
  • Session cookies are HTTP-only.
  • Stripe webhook signatures are verified.
  • Telegram webhook uses TELEGRAM_WEBHOOK_SECRET via Telegram's secret_token header.
  • Protected valuation routes check ownership.
  • Uploaded photos are stored under public/uploads, which is gitignored.

Deployment Notes

For a proper deployment:

  1. Use a stable HTTPS domain.
  2. Set NEXT_PUBLIC_BASE_URL to that domain.
  3. Set Stripe webhook endpoint to:
https://your-domain.com/api/stripe/webhook
  1. Set Telegram webhook once:
https://your-domain.com/api/telegram/webhook
  1. Use production Stripe keys and production STRIPE_WEBHOOK_SECRET.
  2. Run database migrations against the production DB.
  3. Run:
npm run build
npm start

License

Private project. All rights reserved.

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages