didi-website-erp-crm/website/INSTALL.md

9 KiB

Instalare Website DiDi de la 0

Build complet pentru website-ul Next.js, integrat cu ERPNext + Stripe + Keycloak.

Prerequisite

Software pe host

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:

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

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:

# ERPNext (intern via Docker DNS)
NEXT_PUBLIC_ERPNEXT_URL=http://localhost:8080
ERPNEXT_API_URL=http://didi-erpnext:8080
ERPNEXT_API_KEY=<token API key>
ERPNEXT_API_SECRET=<token secret>

# Keycloak
AUTH_KEYCLOAK_ID=didi-website-server
AUTH_KEYCLOAK_SECRET=<secret>
AUTH_KEYCLOAK_ISSUER=https://<KEYCLOAK_HOST>/auth/realms/didi-clients
AUTH_SECRET=<random 32 chars>
AUTH_TRUST_HOST=true
AUTH_URL=http://<HOST>: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://<HOST>:3000
NEXT_PUBLIC_DEFAULT_LOCALE=ro

Pentru valori complete vezi CONFIGURATION.md.

Pas 2. Build + run

cd ~/website
docker compose up -d --build

Build-ul dureaza ~30 secunde (Next.js compile + tsc + static generation).

Pas 3. Verificare

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:

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": "<acelasi cu AUTH_KEYCLOAK_SECRET>",
    "publicClient": false,
    "redirectUris": ["http://<HOST>:3000/*"],
    "webOrigins": ["http://<HOST>: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:

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)

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:

stripe listen --forward-to http://<HOST>: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:

ERPNEXT_API_KEY=85e12d367334775
ERPNEXT_API_SECRET=910a2dfda16d2af

Dezvoltare locala

Daca vrei sa schimbi codul fara rebuild de docker:

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
docker exec didi-website wget -qO- https://<KEYCLOAK_HOST>/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 <HOST>:3000, nu mix de localhost/IP).

Pagina /dashboard/credits arata 0

Userul Keycloak nu are inregistrare in bos_sysadmin.internet_user. Trebuie seedat:

-- 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, '<keycloak_uuid>');

Sau Auth.js auto-creeaza la primul login daca framework e configurat asa.

Build esueaza cu "Module not found"

docker compose build --no-cache website

Site afiseaza versiune veche dupa rebuild

docker compose down website
docker image rm website-website
docker compose up -d --build website

Reset complet

cd ~/website
docker compose down
docker image rm website-website 2>/dev/null
bash build-from-zero.sh