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) - vezierp_crm/INSTALL.md - Keycloak (
didi-keycloak:8080sau 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:
- Verifica
.env.production(creeaza-l din template daca lipseste) - Build imagine Docker (multi-stage: node:20-alpine -> next build -> prod)
- Start container
- 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_KEYsk_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:
AUTH_KEYCLOAK_ISSUERe accesibil din container (curl din container)AUTH_SECRETsetat la 32+ caractere random- Realm + Client exista in Keycloak
redirectUrisin 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