ecommerce

Reetle: de la primul commit la pre-comenzi deschise în paisprezece săptămâni, cu magazin, checkout și CRM propriu

Paisprezece săptămâni pentru infrastructura comercială a unui produs hardware: magazin Angular cu randare pe server, checkout Stripe cu autorizare la pre-comandă, CRM propriu și legătura automată cu depozitul. Inclusiv starea de lansare care a decuplat software-ul de certificare, Norvegia pe care YAML a transformat-o în valoare booleană și cele trei bug-uri pe care le-am prins abia în producție.

Cuprins

Contextul

Reetle face un dispozitiv care se prinde magnetic pe spatele telefonului: un ecran E-Ink de 3,97 inch, cu baterie proprie, care pornește înregistrarea la o singură apăsare, o transcrie, o rezumă și scoate lista de acțiuni din ce s-a discutat. Produsul fusese deja finanțat pe Kickstarter, cu peste 90.000 de dolari strânși de la peste 700 de susținători și livrări în treisprezece țări. Ce lipsea era infrastructura comercială pentru lansarea europeană.

Cerința era completă, nu parțială: magazin public, checkout cu plată, CRM propriu cu segmentare și automatizări, plus legătura cu depozitul care expediază efectiv coletul. Primul commit e din 24 mai 2026. Pe 9 iulie site-ul era în producție, în regim de listă de așteptare. Pe 28 august 2026 s-au deschis pre-comenzile, pe același cod, fără o nouă lansare.

Prima decizie: construit, nu închiriat

Varianta evidentă era Shopify Plus cu Klaviyo deasupra. Am evaluat-o serios, am scris comparația și am respins-o, din trei motive pe care le-am pus în scris înainte să înceapă lucrul.

  • CRM-ul cerut nu era un raport, era un sistem. Unificarea identității clientului, segmentare RFM, declanșatoare de automatizare, atribuire de venit pe fiecare flux. Pe stiva închiriată, "CRM-ul" devine o fereastră fără drept de scriere peste două sisteme străine, iar unificarea reală de identitate cere export și reîmbinare nocturnă a datelor. Ar fi costat aproape cât construcția de la zero.
  • Comutatorul de lansare trebuia să fie o singură valoare. Detaliem mai jos de ce a contat atât de mult.
  • 2.300 de euro pe lună, recurent, față de un cost unic de construcție. Un abonament de platformă nu se termină niciodată, iar migrarea de pe el peste doi ani e o lucrare în sine, ca migrarea Elisara.

Am scris și ce pierdem, pentru că o decizie fără costuri declarate nu e o decizie. Pierdem aproximativ trei săptămâni de viteză față de calea închiriată. Rămânem cu mai multă suprafață de întreținut intern. Și semnăm noi autoevaluarea PCI, chiar dacă nu vedem niciodată datele cardului.

Certificarea nu se grăbește. Software-ul nu are voie să o aștepte.

Orice produs hardware vândut în Europa are nevoie de certificare, iar certificarea se termină când se termină. Nimeni nu o poate promite pe o dată fixă. Problema clasică e că întreaga lansare comercială ajunge legată de acel calendar, iar echipa de software fie stă, fie livrează pe o dată pe care nu o controlează.

Am rupt legătura. Site-ul are o singură valoare de configurare cu trei stări: listă de așteptare, pre-comandă, vânzare deschisă. Se schimbă din panoul de administrare, într-un singur clic, fără repunere în producție și fără atingerea codului. Fiecare pagină, fiecare formular și fiecare email știu să se comporte diferit în funcție de starea curentă.

Partea de bani respectă aceeași logică. La pre-comandă cardul este doar autorizat, nu debitat. Suma se încasează efectiv abia când unitatea pleacă spre client. Dacă termenul comunicat nu este respectat, autorizarea expiră și clientul poate anula oricând înainte de debitare.

Ce am construit, concret

Trei aplicații care împart o singură bază de date și un singur contract de API.

  • Magazinul public, Angular cu randare pe server și 92 de rute pregenerate la build. Pagina de produs, checkout-ul, paginile legale, ghidurile și întrebările frecvente.
  • Backend-ul, Spring Boot peste Postgres: catalog, coș, comenzi, plăți, emailuri tranzacționale, motorul de automatizări și expedierea către depozit.
  • CRM-ul, aplicație separată de administrare: comenzi, clienți, campanii, coduri promoționale plus comisioanele creatorilor, rapoarte și starea fiecărei livrări.

Cifrele de la data publicării acestui text: 44 de migrări de bază de date aplicate în ordine, 23 de decizii de arhitectură scrise și datate, 11 sprinturi și 2.305 de teste automate care rulează la fiecare modificare (1.007 pe backend, 798 pe magazin, 500 pe CRM). Numărul de teste nu e o medalie. E singurul motiv pentru care o valoare de configurare schimbată vineri seara nu ne ține treji.

Plata, livrarea și țara care nu exista

Plățile trec prin Stripe, cu câmpurile de card randate de ei, deci datele cardului nu ating niciodată serverele noastre. Livrarea are două zone, opt dolari fiecare, gratuit peste 150 de dolari.

Aici am schimbat o decizie inițială și merită spus de ce. Prima variantă avea o listă explicită de țări deservite, iar restul lumii primea un refuz. Am întors regula: zona globală este acum implicită, deci o destinație care nu apare pe nicio listă primește totuși un preț. În locul listei de permisiuni am pus o listă de interdicții, verificată înaintea calculului de zonă, care conține strict țările aflate sub embargo complet. Efectul practic a fost că aproximativ 180 de țări au devenit livrabile peste noapte.

Comanda plătită trebuie să ajungă în depozit fără să o mute cineva cu mâna

Depozitul care expediază este al partenerului clientului, cu propriul API. Legătura dintre o comandă plătită și un colet care pleacă este locul unde platformele de comerț pierd cel mai des bani, pentru că e singurul punct în care o eroare tehnică se transformă direct în client care nu primește produsul.

Am construit-o ca o coadă persistentă în baza de date, tiparul cunoscut ca transactional outbox. Când plata e confirmată, în aceeași tranzacție se scrie și rândul de expediere. Atât. Niciun apel către furnizor nu se face în interiorul tranzacției, pentru că o cerere HTTP cu așteptare de zece secunde ar bloca o conexiune din bazin exact când sistemul e sub presiune. Un proces separat golește coada, în loturi, cu reîncercări la unu, două, patru, opt și șaisprezece minute.

Două reguli, ambele învățate din documentația furnizorului și din felul în care se comportă API-urile de logistică sub încărcare:

  • Nu recreăm niciodată orbește. O eroare 500 la creare poate să însemne că, de fapt, comanda a fost creată. Fiecare reîncercare interoghează întâi existența comenzii și abia apoi decide. Numărul comenzii noastre e cheia de idempotență, deci adopția unei comenzi deja create e sigură.
  • Erorile permanente nu consumă reîncercări. Un cod de produs fără corespondent în harta de coduri, sau o adresă incompletă, nu se rezolvă prin repetare. Rândul este parcat imediat, cu alertă către operator. O strategie de reîncercare care repetă și ce nu are cum să reușească doar întârzie momentul în care cineva se uită la problemă.

Cea mai instructivă parte a fost o casetă din formularul furnizorului: provincia destinatarului, obligatorie pentru actele vamale, pe care checkout-ul nostru nu o cerea niciodată. Am construit o componentă care o deduce: în Statele Unite din primele trei cifre ale codului poștal, în Canada din prima literă a codului, în Australia din intervalele de coduri. Restul lumii primește orașul. Iar când nimic nu se potrivește, sistemul nu inventează. Parchează comanda cu eroare și cheamă un om, pentru că o valoare ghicită pe un act vamal e mai scumpă decât o comandă întârziată cu două ore.

Trei bug-uri care merită povestite

Le scriem pentru că partea utilă dintr-o lucrare nu e lista de funcționalități, ci ce s-a stricat și de ce.

Primul: pagina pregenerată a otrăvit toată aplicația. Procesele care generează paginile la build primesc un mediu curățat de variabile, deci nu ajungeau la API. Cererea eșua, răspunsul era memorat ca stare de eroare și starea aceea a fost coaptă în toate cele 92 de pagini pregenerate. În browser, după hidratare, catalogul citea starea otrăvită, iar mecanismul de partajare a rezultatului o fixa pentru toată durata sesiunii, fără nicio cale de reîncercare. Checkout-ul nu găsea produsul și trimitea utilizatorul înapoi pe prima pagină. Se manifesta doar dacă ajungeai la produs dând clic din pagina de start, pentru că o intrare directă pe checkout nu atingea nicio pagină pregenerată și mergea perfect. Două corecturi, ambele generale: o stare pregenerată se crede doar dacă e încărcată cu succes, iar un eșec nu se memorează niciodată.

Al doilea: meniul mobil era prizonier într-un efect vizual. Antetul avea un filtru de fundal aplicat. Un element cu backdrop-filter devine bloc conținător pentru descendenții cu position: fixed, iar meniul lateral trăia înăuntrul antetului. Deci meniul, care cerea tot ecranul, se raporta la cutia de 64 de pixeli a antetului. Măsurat pe un telefon real: panoul avea 64 de pixeli înălțime, lista de linkuri se calcula la zero, iar conținutul paginii se vedea prin meniu. Corectura a fost mutarea meniului afară din antet, ca element frate.

Al treilea: titlul de pe telefon nu s-a micșorat niciodată. Formula de dimensionare avea o limită de jos de 48 de pixeli, iar partea proporțională cu lățimea ecranului depășea limita aceea abia peste 600 de pixeli. Rezultatul: fiecare telefon primea exact același titlu de 48 de pixeli, care se rupea pe trei rânduri și împingea blocul de text în zona unde voalul de peste imagine era aproape transparent. Text alb pe video luminos. În plus, aceleași telefoane primeau un fișier video de 1093 KB. Atributul media de pe <source> este respectat doar în interiorul unui <picture>; într-un <video> browserul ia pur și simplu prima sursă, deci selecția scrisă doar în marcaj nu funcționează. Am mutat alegerea în cod, cu o variantă de 368 KB pentru ecrane mici.

Ce am tăiat înainte de lansare

Magazinul a fost construit în șase limbi, duse toate la paritate completă de chei, cu traduceri și în baza de date, nu doar în interfață. Cu două săptămâni înainte de deschiderea pre-comenzilor, clientul a cerut lansare doar în engleză și decontare doar în dolari.

Am făcut-o și am scris decizia. Rutele de limbă retrase redirecționează definitiv către echivalentul englez, comutatorul de limbă și semnalele hreflang au rămas pe o singură limbă, iar traducerile au rămas în cod, inactive, gata de reactivat după revizuirea cu vorbitori nativi. Munca nu s-a pierdut, dar a fost făcută mai devreme decât trebuia. Concluzia pe care o ducem mai departe: internaționalizarea se scrie în arhitectură de la început, se activează la sfârșit, iar limbile se aprind una câte una, când există cine să le corecteze.

Unde suntem acum

Pe 28 august 2026, ereetle.com a deschis pre-comenzile pentru Reetle SmartInk I, la 149 de dolari, cu debitare la expediere. Magazinul, panoul de administrare și legătura cu depozitul rulează pe aceeași infrastructură din iulie.

Ce urmează: activarea în producție a integrării cu depozitul, revizuirea traducerilor cu vorbitori nativi înaintea redeschiderii celorlalte limbi și manualul de utilizare în pagina de produs. Raportarea rămâne aceeași, inclusiv în săptămânile în care ceva scade. Livrarea e 40% din treabă; restul e ca lucrurile să meargă curat luni la rând (A.06).

Întrebări frecvente

Cât durează construirea unei platforme de e-commerce de la zero?

Pentru Reetle, de la primul commit la site în producție au trecut sub șapte săptămâni, iar până la deschiderea pre-comenzilor paisprezece. Diferența dintre cele două momente nu e cod scris mai încet, ci restul lucrării: plăți testate cap la cap, CRM, legătura cu depozitul, conținut legal și traduceri. Termenul realist pentru un magazin construit la comandă, cu panou propriu de administrare, e de două până la patru luni, în funcție de câte integrări externe intră în calcul.

De ce să construiești un magazin la comandă, dacă o platformă gata făcută e mai rapidă?

Pentru că diferența se vede în ce ai voie să faci cu datele, nu în cât de repede pui primul produs pe site. O platformă închiriată e alegerea corectă când vinzi un catalog standard și ai nevoie de viteză. Devine costisitoare când ai nevoie de unificare reală a identității clientului, de automatizări proprii sau de o stare de lansare pe care o controlezi tu. La Reetle, cerința de CRM cu segmentare și atribuire de venit era chiar motivul pentru care platforma închiriată nu ajungea, iar abonamentul de 2.300 de euro pe lună nu se termină niciodată.

Cum vinzi un produs hardware care încă nu are certificarea finalizată?

Cu pre-comandă și autorizare de card, nu cu debitare. Cardul e autorizat în momentul comenzii și debitat abia când unitatea pleacă spre client; dacă termenul comunicat nu e respectat, autorizarea expiră și clientul poate anula. Partea tehnică e ca site-ul să aibă o stare de lansare explicită, salvată în baza de date, care schimbă simultan ce arată paginile, ce acceptă checkout-ul și ce scrie în emailuri. Așa certificarea și lansarea software nu se mai blochează una pe cealaltă.