# Instalare Website DiDi de la 0 Build complet pentru website-ul Next.js, integrat cu ERPNext + Stripe + Keycloak. ## Prerequisite ### Software pe host ```bash docker --version # >= 24.0 docker compose version # >= v2 ``` NU e nevoie de Node.js pe host - se construieste in container. ### Resurse - 2 GB RAM - 5 GB spatiu disc - Port 3000 liber ### Retea Docker Aceeasi retea cu ERP/CRM: ```bash docker network create didi-network 2>/dev/null || true ``` ### Servicii dependinte (trebuie sa ruleze inainte) - **ERPNext** (`didi-erpnext:8080`) - vezi `erp_crm/INSTALL.md` - **Keycloak** (`didi-keycloak:8080` sau extern) - **Stripe** cont (cheile API in .env.production) - **DiDi Platform** (agent-v3) sau accesibil prin URL Daca rulezi separat, website-ul porneste oricum dar paginile auth/checkout vor da erori. ## Build automat ```bash cd ~/website bash build-from-zero.sh ``` Scriptul: 1. Verifica `.env.production` (creeaza-l din template daca lipseste) 2. Build imagine Docker (multi-stage: node:20-alpine -> next build -> prod) 3. Start container 4. Health check pe http://localhost:3000 ## Build manual ### Pas 1. `.env.production` Editeaza `~/website/.env.production`. Variabilele critice: ```env # ERPNext (intern via Docker DNS) NEXT_PUBLIC_ERPNEXT_URL=http://localhost:8080 ERPNEXT_API_URL=http://didi-erpnext:8080 ERPNEXT_API_KEY= ERPNEXT_API_SECRET= # Keycloak AUTH_KEYCLOAK_ID=didi-website-server AUTH_KEYCLOAK_SECRET= AUTH_KEYCLOAK_ISSUER=https:///auth/realms/didi-clients AUTH_SECRET= AUTH_TRUST_HOST=true AUTH_URL=http://:3000 # DiDi API (intern) DIDI_API_URL=http://didi-agent-v3:24803/api DIDI_FRAMEWORK_URL=http://didi-framework:3005 # Stripe NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY=pk_test_... STRIPE_SECRET_KEY=sk_test_... STRIPE_WEBHOOK_SECRET=whsec_... # Stripe subscriptions STRIPE_PRICE_PAID_MONTHLY=price_xxx STRIPE_PRICE_PAID_YEARLY=price_xxx STRIPE_PRICE_ENTERPRISE_MONTHLY=price_xxx STRIPE_PRICE_ENTERPRISE_YEARLY=price_xxx # Site NEXT_PUBLIC_SITE_URL=http://:3000 NEXT_PUBLIC_DEFAULT_LOCALE=ro ``` Pentru valori complete vezi [CONFIGURATION.md](./CONFIGURATION.md). ### Pas 2. Build + run ```bash cd ~/website docker compose up -d --build ``` Build-ul dureaza ~30 secunde (Next.js compile + tsc + static generation). ### Pas 3. Verificare ```bash sleep 10 curl -s -o /dev/null -w "%{http_code}\n" http://localhost:3000 # Trebuie sa fie 200 ``` Browser: http://localhost:3000 ## Configurare Keycloak (one-time) Pe platforma reala, realm-ul utilizatorilor este **`didi-clients`** (issuer cu prefix `/auth`). Pentru dezvoltare locala (mod DEMO cu mock), creeaza un realm local cu acelasi nume: ```bash KC_TOKEN=$(curl -s -X POST http://localhost:28080/realms/master/protocol/openid-connect/token \ -d "client_id=admin-cli&username=admin&password=admin123&grant_type=password" \ | python3 -c "import sys,json; print(json.load(sys.stdin)['access_token'])") # Realm curl -s -X POST http://localhost:28080/admin/realms \ -H "Authorization: Bearer $KC_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "realm": "didi-clients", "enabled": true, "registrationAllowed": true, "resetPasswordAllowed": true }' # Client confidential curl -s -X POST http://localhost:28080/admin/realms/didi-clients/clients \ -H "Authorization: Bearer $KC_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "clientId": "didi-website-server", "secret": "", "publicClient": false, "redirectUris": ["http://:3000/*"], "webOrigins": ["http://:3000"], "standardFlowEnabled": true, "directAccessGrantsEnabled": true }' # Test user curl -s -X POST http://localhost:28080/admin/realms/didi-clients/users \ -H "Authorization: Bearer $KC_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "username": "test@didi.local", "email": "test@didi.local", "firstName": "Test", "lastName": "User", "enabled": true, "emailVerified": true, "credentials": [{"type": "password", "value": "Test1234!", "temporary": false}] }' ``` ## Configurare Stripe (one-time) ### Pas 1. Cont Stripe Creeaza cont la https://stripe.com si activeaza modul test. ### Pas 2. Cheia API Dashboard > Developers > API keys > "Reveal test key" - `pk_test_xxx` (publishable) -> `NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY` - `sk_test_xxx` (secret) -> `STRIPE_SECRET_KEY` ### Pas 3. Produse one-time In `src/lib/stripe.ts` exista o mapare `PRICE_MAP` cu 14 price IDs. Acestea trebuie sa existe in contul tau Stripe. Pentru a le crea automat: ```bash docker exec -i didi-website node -e " const Stripe = require('stripe').default || require('stripe'); const stripe = new Stripe(process.env.STRIPE_SECRET_KEY); const ITEMS = [ ['techniques-text', 'Techniques - Text', 5000], ['techniques-image', 'Techniques - Image', 7500], // ... vezi PRICE_MAP pentru lista completa ]; (async () => { for (const [key, name, amount] of ITEMS) { const p = await stripe.products.create({ name, metadata: { service_key: key }}); const price = await stripe.prices.create({ product: p.id, unit_amount: amount, currency: 'ron' }); console.log(key, '->', price.id); } })(); " ``` ### Pas 4. Produse recurente (subscriptii) ```bash docker exec -i didi-website node -e " const Stripe = require('stripe').default || require('stripe'); const stripe = new Stripe(process.env.STRIPE_SECRET_KEY); const PLANS = [ { key: 'paid-monthly', name: 'DiDi Paid - Lunar', amount: 9900, interval: 'month' }, { key: 'paid-yearly', name: 'DiDi Paid - Anual', amount: 99900, interval: 'year' }, { key: 'enterprise-monthly', name: 'DiDi Enterprise - Lunar', amount: 49900, interval: 'month' }, { key: 'enterprise-yearly', name: 'DiDi Enterprise - Anual', amount: 499000, interval: 'year' }, ]; (async () => { for (const p of PLANS) { const prod = await stripe.products.create({ name: p.name, metadata: { plan_key: p.key }}); const price = await stripe.prices.create({ product: prod.id, unit_amount: p.amount, currency: 'ron', recurring: { interval: p.interval } }); console.log(p.key, '->', price.id); } })(); " ``` Apoi pune ID-urile in `.env.production` la `STRIPE_PRICE_PAID_MONTHLY` etc. ### Pas 5. Webhook (la productie) In dev local NU primesti webhook-uri Stripe (nu poate ajunge la masina ta). La productie: - Dashboard Stripe > Developers > Webhooks > Add endpoint - URL: `https://didi.tld/api/webhooks/stripe` - Events: `checkout.session.completed`, `customer.subscription.created`, `customer.subscription.updated`, `customer.subscription.deleted`, `invoice.paid`, `invoice.payment_failed` - Copy signing secret in `STRIPE_WEBHOOK_SECRET` Pentru dev local, foloseste Stripe CLI: ```bash stripe listen --forward-to http://:3000/api/webhooks/stripe ``` ## Configurare ERPNext API key (one-time) In ERPNext: Setup > User > Administrator > API Access > Generate Keys Salveaza API Key + Secret in `.env.production`: ```env ERPNEXT_API_KEY=85e12d367334775 ERPNEXT_API_SECRET=910a2dfda16d2af ``` ## Dezvoltare locala Daca vrei sa schimbi codul fara rebuild de docker: ```bash cd ~/website npm install cp .env.production .env.local # Edit .env.local pentru localhost-uri (ERPNEXT_API_URL=http://localhost:8080) npm run dev ``` Acceseaza http://localhost:3000 (sau alt port daca 3000 e ocupat). ## Probleme frecvente ### Login: "Server error - problem with server configuration" Verifica: 1. `AUTH_KEYCLOAK_ISSUER` e accesibil din container (curl din container) 2. `AUTH_SECRET` setat la 32+ caractere random 3. Realm + Client exista in Keycloak 4. `redirectUris` in client include URL-ul tau ```bash docker exec didi-website wget -qO- https:///auth/realms/didi-clients/.well-known/openid-configuration | python3 -m json.tool ``` ### Login redirecteaza la /login dupa Keycloak Cookies corupte. Hard refresh (Ctrl+Shift+R) sau modă incognito. ### Stripe Checkout duce inapoi la /login `NEXT_PUBLIC_SITE_URL` are alt host decat cel din browser. Asigura-te ca acceseaza acelasi host (ex: tot `:3000`, nu mix de localhost/IP). ### Pagina `/dashboard/credits` arata 0 Userul Keycloak nu are inregistrare in `bos_sysadmin.internet_user`. Trebuie seedat: ```sql -- PG: didiFramework DB INSERT INTO bos_sysadmin.internet_user (internet_user_id, person_id, credits_remained, credits_spent, storage_limit_bytes) VALUES (90000, 90000, 100, 0, 1073741824); INSERT INTO bos_sysadmin.user_credential (internet_user_id, email, enrollment_type, cellular_phone_no, no_attempts_failed, subscription_status, activation_date, keycloak_id) VALUES (90000, 'test@didi.local', 1, '', 0, 1, CURRENT_DATE, ''); ``` Sau Auth.js auto-creeaza la primul login daca framework e configurat asa. ### Build esueaza cu "Module not found" ```bash docker compose build --no-cache website ``` ### Site afiseaza versiune veche dupa rebuild ```bash docker compose down website docker image rm website-website docker compose up -d --build website ``` ## Reset complet ```bash cd ~/website docker compose down docker image rm website-website 2>/dev/null bash build-from-zero.sh ```