Как я, не будучи программистом, за несколько недель собрал медицинское приложение на связке GitHub Pages + Cloudflare Workers + Claude API — и что пошло не так.

Как я, не будучи программистом, за несколько недель собрал медицинское приложение на связке GitHub Pages + Cloudflare Workers + Claude API — и что пошло не так.

Полгода назад я не написал ни строчки кода. Сегодня у меня есть работающее веб-приложение, которое анализирует результаты анализов крови с помощью ИИ, ведёт дневник давления, сахара и лекарств, принимает оплату подписки через Stripe и устанавливается на телефон как обычное приложение. Ниже — подробно о том, как это было устроено технически, какие ошибки я допустил по пути и что бы сделал иначе, если бы начинал сейчас.

С чего всё началось

Дома скопилась пачка результатов анализов за несколько лет — часть в бумажном виде, часть в PDF. Каждый раз одно и то же: смотришь на цифры, не понимаешь, что они значат в комплексе, гуглишь отдельные показатели, забываешь через неделю. Возникла простая мысль: а что, если отдать всю эту пачку ИИ и попросить объяснить по-человечески, что происходит. Я не программист. Но оказалось, что для реализации такой идеи сейчас это не обязательное условие — если понимать, из каких компонентов должна состоять система, и быть готовым разбираться с документацией методом проб и ошибок.

Архитектура: три бесплатных сервиса вместо сервера

Схема получилась такая:

  • GitHub Pages — статический хостинг для самого интерфейса (HTML/CSS/JS). Бесплатно, надёжно, не требует сервера.
  • Cloudflare Workers — serverless-прослойка между приложением и внешними API. Ключи хранятся в его переменных окружения на стороне Cloudflare, а браузер обращается только к самому Worker'у.
  • Anthropic API (Claude) — обработка текста/фото/PDF с анализами и генерация понятного разбора.
  • Stripe — приём платежей за подписку Premium.

Ничего из этого не требует классического сервера или базы данных — вся пользовательская история хранится в браузере пользователя (localStorage), а не на моей стороне. Это одновременно и плюс (простота, нет затрат на хранение, меньше ответственности за утечки), и ограничение (данные не синхронизируются между устройствами).

Первая серьёзная проблема: дыра в проверке оплаты

Когда я только запустил Stripe Payment Link, логика включения Premium была наивной: после успешной оплаты Stripe перенаправлял пользователя обратно на сайт с параметром ?premium=1 в ссылке, и код просто проверял его наличие:

if(urlParams.get('premium')==='1'){ localStorage.setItem('ms_premium','true'); }

Проблема очевидна, как только её произносишь вслух: любой человек мог вручную вписать ?premium=1 в адресную строку и получить Premium бесплатно, без всякой оплаты. Ссылка не проверяла, была ли оплата реальной.

Решение — добавить в Worker отдельный эндпоинт /verify-payment, который принимает session_id (Stripe присылает его при возврате после оплаты) и обращается напрямую к Stripe API, чтобы узнать реальный статус этой конкретной сессии:

const stripeResp = await fetch( `https://api.stripe.com/v1/checkout/sessions/${sessionId}`, { headers: { 'Authorization': 'Bearer ' + env.STRIPE_SECRET_KEY } } ); const session = await stripeResp.json(); const paid = session.payment_status === 'paid' || session.status === 'complete';

Только если Stripe подтверждает paid: true для конкретного session_id — фронтенд включает Premium. Подделать это уже нельзя: session_id привязан к конкретной транзакции, и без реальной оплаты Stripe никогда не вернёт paid: true.

Урок: если платная функциональность в приложении включается по параметру в URL — это по умолчанию небезопасно, вне зависимости от того, насколько "неочевидным" кажется этот параметр. Любая проверка авторизации/оплаты должна происходить на сервере, обращающемся к первоисточнику данных, а не полагаться на то, что пришло в браузер.

PWA вместо магазинов приложений

Следующий вопрос — как дать пользователям что-то похожее на "установленное приложение", не проходя долгий путь модерации Google Play и App Store (тем более, что App Store особенно жёстко относится к приложениям, которые выглядят как обёртка над сайтом, и требует отдельной интеграции своей платёжной системы вместо Stripe для части стран).

Ответ — Progressive Web App. Это заняло на удивление немного работы:

— manifest.json с описанием приложения, иконками и цветовой темой

— sw.js — простой service worker, кэширующий статику для офлайн-доступа

— Пара строк в HTML для регистрации service worker и подключения манифеста

В результате при открытии сайта в Chrome на Android браузер сам предлагает «Установить приложение» —без всякого магазина. На iPhone то же самое доступно через Safari → «Поделиться» → «На экран Домой». После установки приложение открывается в полноэкранном режиме, без адресной строки — визуально неотличимо от нативного.

Для проекта на самой ранней стадии, без бюджета на разработку, это выглядит разумным промежуточным решением: закрывает главную пользу от "быть приложением", ничего не стоит и не требует ожидания модерации.

Первый выход к пользователям — и урок про модерацию соцсетей

Когда базовая функциональность и защита оплаты были готовы, я опубликовал пост о проекте на одной из крупных российских площадок с пользовательским контентом. В посте была история создания, описание функциональности и — в том числе — упоминание цены подписки.

Через несколько часов аккаунт был заблокирован навсегда за нарушение правил размещения рекламы. Формально пост действительно содержал признаки продвижения коммерческого продукта: прямую ссылку, упоминание цены, призыв попробовать.

Урок оказался полезным для дальнейших публикаций: на большинстве площадок с пользовательским контентом граница между "личной историей о своём проекте" и "рекламой" определяется формальными признаками — наличием цены, прямого CTA купить, ссылки без контекста. Если хочется избежать модерационных рисков:


  • Не указывать конкретную стоимость в тексте
  • Делать акцент на процессе и деталях реализации, а не на призыве попробовать продукт
  • Заранее знакомиться с правилами конкретной площадки

Что дальше

Сейчас в работе:

  • Английская версия интерфейса — для выхода за пределы русскоязычной аудитории
  • Сборка через TWA для публикации в Google Play (после того, как появится первая группа реальных пользователей)
  • Отдельная страница политики конфиденциальности — обязательный элемент доверия

для любого приложения, работающего с данными о здоровье.

Если интересно посмотреть, что получилось: https://aleksa09022019-dotcom.github.io/analyzes/MedScan.html. Оно бесплатно для базового использования, более глубокий ИИ-анализ трендов и совместимости лекарств доступен по платной подписке.

Отдельно подчеркну: это не медицинское устройство и не замена консультации врача — ИИ-разбор носит информационный характер, дисклеймер об этом есть прямо в интерфейсе.

Буду рад вопросам и критике в комментариях — особенно от тех, кто тоже собирал что-то на no-code инструментах и сталкивался с похожими граблями.

22