SPA + API for product checkout with resilient payment recovery flow.
- Public repository:
https://github.com/Sebascerpo/test - Frontend deployed URL:
https://d31hbsczosda21.cloudfront.net/ - Backend deployed URL:
http://payment-store-alb-1512441505.us-east-1.elb.amazonaws.com/health - Public API docs URL:
https://d31hbsczosda21.cloudfront.net/api/docs
Note: Production routing serves frontend and API behind the same CloudFront domain (/api/*).
- Frontend: React + Redux Toolkit + Vite
- Backend: NestJS + TypeScript
- Database: PostgreSQL + TypeORM
- Frontend guide:
frontend/README.md - Backend guide:
backend/README.md
Hexagonal (Ports & Adapters) structure:
domain: entities and enums.application: use cases and ports.infrastructure: controllers, TypeORM entities/repositories, payment adapters.shared: ROP result helpers and cross-cutting utilities.
Tables:
products: catalog withstock.customers: buyer and delivery profile snapshot source.transactions: payment lifecycle (PENDING,APPROVED,DECLINED,ERROR).deliveries: internal assignment record per transaction (uniquetransaction_id).
transactions.product_id -> products.idtransactions.customer_id -> customers.iddeliveries.transaction_id -> transactions.id(1:1 logical via unique)deliveries.product_id -> products.iddeliveries.customer_id -> customers.id
Swagger UI:
- Public:
https://d31hbsczosda21.cloudfront.net/api/docs - Local:
http://localhost:3002/api/docs
Core endpoints:
GET /api/app/configGET /api/productsGET /api/products/:idPOST /api/customersGET /api/customers/:idGET /api/customers/email/:emailPOST /api/payment/processGET /api/transactionsGET /api/transactions/:idGET /api/transactions/reference/:referenceGET /api/transactions/reference/:reference/syncGET /api/deliveries/:idGET /api/deliveries/transaction/:transactionReferencePATCH /api/deliveries/:id/status
- Backend creates local
PENDINGtransaction with a reference. - Backend calls provider APIs (tokenize card, payment source, transaction).
- Backend finalizes transaction status.
- Backend creates/updates delivery assignment when status is final.
- Backend decrements stock only if transaction is
APPROVED. - Frontend consumes final status and returns to product page with updated stock.
Date: March 2, 2026
- Frontend:
https://d31hbsczosda21.cloudfront.net/ - Backend (proxy):
https://d31hbsczosda21.cloudfront.net/api - Swagger:
https://d31hbsczosda21.cloudfront.net/api/docs
Steps:
- Open catalog and select a product with stock.
- Complete card + delivery data.
- Click
Pagarin summary. - Wait for final status.
- Return to product page and verify updated stock.
Expected:
- Backend creates
PENDINGtransaction first. - Payment resolves to terminal state.
- Delivery assignment is created.
- Stock updates only on
APPROVED.
Result:
- PASS (validated in deployed environment).
- For approved-flow validation in current sandbox tests, use card number
4242424242424242.
Steps:
- Click
Pagar. - Refresh page while transaction is still pending.
- Verify recovery to final transaction status.
Expected:
- No duplicate charge flow.
- Pending reference is recovered from persisted state.
- Frontend syncs status and shows final result.
Result:
- PASS (validated in deployed environment).
Steps:
- Reach summary step.
- Disable network and click
Pagar.
Expected:
- UI does not freeze.
- Structured error/feedback shown in Spanish.
- Checkout context remains recoverable.
Result:
- PASS (validated in deployed environment).
- Idempotent
referencesupport to avoid duplicate charges on retries/reloads. - Sync endpoint returns
200retryable states for transient visibility windows:transaction: null,retryable: true,reason: "NOT_FOUND_YET".
- State management: Redux Toolkit +
redux-persist(Flux-aligned action/thunk flow). - Feature-oriented folders:
frontend/src/features/catalog/componentsfrontend/src/features/checkout/componentsfrontend/src/features/transaction/components
- Recovery orchestration:
frontend/src/hooks/usePendingTransactionRecovery.ts- Deduplicated sync in StrictMode (
isSyncing,lastSyncAt,syncReference).
- Payment flow security split:
- Persisted draft: product, delivery, checkout step, pending reference,
cardPreview. - In-memory only (
sensitiveSession): PAN/CVC runtime card payload.
- Persisted draft: product, delivery, checkout step, pending reference,
- PAN/CVC are never persisted in
localStorage. - Persisted card data is restricted to metadata (
brand,last4, holder, expiry). - If runtime card data is missing (e.g. after refresh), checkout routes back to card entry with Spanish UX guidance.
- Offline and network-drop scenarios are handled via structured ROP-style error results.
cp backend/.env.template backend/.env
cp frontend/.env.template frontend/.envdocker compose up --buildDetached mode (optional):
docker compose up --build -dStop and remove containers:
docker compose downStop and remove containers + DB volume reset:
docker compose down -vServices:
- Frontend:
http://localhost:5173 - Backend:
http://localhost:3002 - Swagger:
http://localhost:3002/api/docs - PostgreSQL:
localhost:5432
Direct backend checks:
curl http://localhost:3002/health
curl http://localhost:3002/api/productsThrough frontend Vite proxy (frontend -> backend):
curl http://localhost:5173/api/productsUseful logs:
docker compose logs -f backend frontendIf payment-backend becomes unhealthy:
- Ensure backend DB SSL is disabled for local Docker Postgres:
DATABASE_SSL=false(or leave it unset, default isfalse).
- Then restart:
docker compose down
docker compose up --build -dcd backend
npm install
npm run start:devcd frontend
npm install
npm run devBackend:
cd backend
npm run test
npm run test:e2e
npm run test:covFrontend:
cd frontend
npm run test
npm run test:covNote: Frontend uses Jest + Testing Library (jest-dom) with thresholds >= 80%.
Latest frontend test run (cd frontend && npm run test:cov on March 2, 2026):
- Test suites:
14 passed, 14 total - Tests:
63 passed, 63 total - Snapshots:
0 total
Latest frontend coverage:
- Statements:
98.39% - Branches:
84.65% - Functions:
97.56% - Lines:
98.39%
Coverage target:
>= 80%(challenge requirement).
Latest backend coverage (cd backend && npm run test:cov on March 2, 2026):
- Statements:
98.96% - Branches:
92.72% - Functions:
100% - Lines:
98.87%
Automated contract checks:
frontend/src/features/responsive/ResponsiveContracts.spec.tsx
| Target viewport | Status | Evidence |
|---|---|---|
| iPhone SE 2020 (375x667) | PASS | Bottom-sheet modal/backdrop + constrained svh heights and no card overflow in checkout components |
| iPhone 12/14 | PASS | Responsive grid/cards + fluid typography and spacing |
| Pixel 7 | PASS | Same mobile-first layout and controls with grid/flex |
| iPad portrait | PASS | Breakpoints (sm/lg) for catalog and checkout surfaces |
| Desktop narrow/wide | PASS | Max-width containers and adaptive catalog columns |
| Check | Status |
|---|---|
| No horizontal overflow in main checkout flow | PASS |
| Touch targets fit boundaries in modal/summary/result | PASS |
| Offline/online transitions keep UI interactive | PASS |
| Product images lazy-load and decode async | PASS |
- Helmet enabled.
- Input validation via global
ValidationPipe. - CORS allowlist configurable via
CORS_ORIGINS.
- Backend is the source of truth for checkout config (
APP_CURRENCY,APP_BASE_FEE,APP_DELIVERY_FEE). - Frontend consumes runtime config via
GET /api/app/configand falls back toVITE_*only if needed. - Product seeder converges to 7 catalog products with image URLs, idempotently by product
name.