Toate articolele
·de · Manilla Services·Next.jsPlăți onlineNetopiaSecuritateStudiu de caz

Cum am integrat Netopia în recepția NovaFit: plăți simple și recurente, cu proxy securizat

Studiu de caz tehnic: cum am adus plățile online și abonamentele cu reînnoire automată într-o aplicație de recepție Next.js, fără ca datele de card să atingă vreodată serverele noastre — arhitectura de proxy, tokenizarea pentru recurent și partea grea, idempotența callback-urilor.

Cum am integrat Netopia în recepția NovaFit: plăți simple și recurente, cu proxy securizat

Recepția unei săli de fitness are un ritm foarte concret: vine un client, alege un abonament, plătește, intră la sală. Când am construit aplicația de recepție pentru NovaFit — o aplicație internă în Next.js, folosită zilnic de fete la birou — a apărut inevitabil întrebarea: cum aducem plata cu cardul, online, fără să transformăm o aplicație de recepție într-un mic procesator de plăți?

Răspunsul a fost o integrare Netopia gândită pe două straturi: plăți simple pentru abonamentele plătite o dată și plăți recurente pentru abonamentele care se reînnoiesc singure. Iar principiul care a stat la baza fiecărei decizii tehnice a fost simplu: datele de card nu trebuie să atingă niciodată serverele noastre.

Plată simplă vs. plată recurentă — și de ce contează pentru o sală

Nu toate abonamentele au același comportament de plată, iar diferența nu e cosmetică.

Plată simplăPlată recurentă
Când se foloseșteAbonament plătit o singură dată, oferte „+1", perioade de testAbonamente lunare, semestriale sau anuale care se reînnoiesc
Ce reține sistemulNimic despre cardUn token de la Netopia, legat de card
La reînnoireClientul plătește din nou manualSistemul taxează tokenul automat
ConsimțământImplicit prin acțiunea de platăExplicit, confirmat de client la recepție

Pentru o sală, recurentul e important pentru că elimină frecarea lunară — clientul nu mai trebuie să vină special sau să reintroducă cardul. Dar tocmai fiindcă e o taxare automată, l-am legat de o regulă fermă: nimic nu se întâmplă cu banii membrului fără știrea și acordul lui. Comutatorul de recurent din ecranul de vânzare se activează doar cu un pas explicit de consimțământ, iar pentru abonamentele mai lungi (semestriale, anuale) acordul e obligatoriu, nu bifat din reflex.

Dacă vrei să vezi cum abordăm integrările de plată în general, am strâns aici partea de integrări de plăți online.

Arhitectura „proxy prin backend": de ce nu vorbește recepția direct cu banca

Cea mai importantă decizie de arhitectură a fost să nu las aplicația de recepție să colecteze sau să transmită date de card. În locul acestui lucru, tranzacția trece printr-un backend propriu care se comportă ca un proxy către Netopia:

De ce contează? Pentru că datele sensibile de card rămân în perimetrul Netopia. Serverul nostru nu le vede, nu le stochează, nu le transportă. În termeni de conformitate, asta reduce dramatic scope-ul PCI — nu poți scurge ce nu deții. Nu e un slogan de securitate, e o consecință directă a designului: mai puțină suprafață, mai puțin de apărat.

Cum arată „sigilarea" în practică

Netopia (protocolul mobilpay) funcționează pe un mecanism de tip plic sigilat. Comanda e un XML pe care îl sigilăm cu o cheie simetrică generată aleator; acea cheie e la rândul ei criptată cu certificatul public al Netopia. Practic, doar Netopia poate deschide plicul. La întoarcere, callback-ul (IPN) e sigilat de Netopia și îl deschidem cu cheia privată a comerciantului — ceea ce ne dă și garanția că mesajul chiar vine de la ei, nu de la un terț care ne bombardează endpoint-ul.

Un detaliu care ne-a dat bătăi de cap merită menționat, pentru că e genul de lucru pe care documentația nu-l spune: pe versiunile recente de Node, o măsură de securitate (legată de CVE-2023-46809, atacul Bleichenbacher) blochează exact modul de decriptare pe care îl cere protocolul. A trebuit să implementăm manual pasul de „despachetare" a plicului, cu grijă la formatul de padding. E un exemplu bun despre ce înseamnă o integrare reală: nu lipești un SDK, ci înțelegi criptografia de dedesubt cât să treci peste cazurile în care lumea reală nu se poartă ca în exemple.

Fluxul concret, de la buton la abonament

Pas cu pas, o vânzare online arată așa:

  1. Inițiere — recepționera alege membrul și tipul de abonament. Dacă e un abonament recurent eligibil, bifează plata recurentă, cu acordul clientului.
  2. Redirect — backend-ul sigilează comanda și trimite clientul pe pagina de plată Netopia.
  3. Plata — clientul introduce cardul pe pagina Netopia; aici se colectează, se validează, se autorizează.
  4. IPN — Netopia trimite callback-ul de confirmare către backend, care marchează comanda plătită.
  5. Confirmare — recepția preia comanda și creează abonamentul în panou, cu tot cu zilele bonus, referral și celelalte reguli de business.

Punctul subtil e că pașii 3–4 se întâmplă în afara aplicației noastre de recepție. Clientul poate închide tabul, îi poate pica netul, poate reveni peste cinci minute. De asta confirmarea nu se bazează pe faptul că omul „se întoarce pe pagina noastră", ci pe callback-ul server-to-server.

Partea grea: idempotență, statusuri, reconciliere

Aici s-a jucat de fapt calitatea integrării.

Idempotența callback-urilor. Un IPN poate sosi de mai multe ori pentru aceeași comandă — asta e normal, nu o eroare. Dacă tratezi naiv fiecare callback, riști să creezi două abonamente pentru o singură plată. Am legat crearea abonamentului de comandă printr-o referință unică, astfel încât al doilea IPN nu mai produce un al doilea abonament. O plată, un abonament, indiferent de câte ori bate Netopia la ușă.

Statusuri clare. O comandă trece prin stări (inițiată → plătită → onorată). Confirmarea plății și crearea abonamentului sunt două lucruri separate, tocmai ca să putem relua sigur pasul al doilea dacă primul a reușit dar al doilea nu.

Reconciliere pentru „a plătit, dar nu s-a văzut". Cazul clasic: clientul plătește pe Netopia, dar închide tabul înainte să revină. Comanda e plătită, dar recepția n-a apucat s-o preia. Am pus un job periodic care caută comenzile plătite dar neonorate și le finalizează singur — creează abonamentul cu un actor „de sistem", ca să fie clar în istoric că nu a fost o fată la birou. În mediul de test acest job e „împământat": nu creează niciodată abonamente reale dintr-o plată sandbox.

Reînnoirea recurentă e, la rândul ei, un job periodic: verifică abonamentele care expiră, confirmă că recurentul e activ și taxează tokenul păstrat de la Netopia — fără să re-colecteze vreodată datele de card. Această parte de „se întâmplă singur, pe fundal" se leagă natural de felul în care gândim automatizările de task-uri repetitive: munca previzibilă și plictisitoare o face sistemul, oamenii se ocupă de clienți.

Rezultatul pentru echipa NovaFit

Pe scurt: recepția poate vinde abonamente cu plată online, iar abonamentele recurente se reînnoiesc singure, fără ca cineva să reintroducă vreun card și fără ca vreo dată sensibilă să treacă prin aplicația sălii. Fetele de la birou au un flux în pași puțini; clientul plătește pe o pagină bancară pe care o recunoaște; iar reconcilierea are grijă de cazurile în care lumea reală o ia razna.

E genul de integrare care nu se vede când merge — și exact asta e ideea. Detaliile complete despre aplicația în sine sunt în studiul de caz NovaFit Recepție.