- 31 Aug, 2026 13 commits
-
-
Fix final-review findings: normalize order total, re-check tolerance against authoritative total, wrap HTTP errors, log orphan orders, add outbound timeout - SobanleClient.createSale: coerce grand_total to a rounded Number and reject non-finite/non-positive values before it crosses into the epay signature and the DB amount, so a decimal-string API response can't desync the signed value from the stored/transmitted one. - productComboPicker.computeLineTotal: coerce tax.rate with Number() instead of a strict typeof check, so a string rate is honored instead of silently defaulting to 0%. - SobanleClient.createAutoOrder: reject with SOBANLE_TOTAL_EXCEEDS_TARGET and log the order context when the POS's authoritative grand_total exceeds targetAmount — the spec's tolerance was only ever checked against the local estimate, never the real total. - SobanleClient.withAuthRetry: wrap non-2xx response bodies into real Error objects with a statusCode, instead of forwarding the raw body as err (previously every real HTTP failure logged `message: undefined` with no status code). - admin.server.controller.js createTransaction: log the Sổ Bán Lẻ sobanleOrderId/total when the follow-up AdminTransaction DB write fails, so an order left orphaned in the POS can be found later. - ApiRequest.js: add timeout: 15000 to the three *OtherUrlWithHeader functions so a hung Sổ Bán Lẻ call can't hang the admin's request indefinitely (also benefits the existing Appota caller). Co-Authored-By:
Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017KPzWwuTEeX2vXGXvyGn4q
tdgiang authored -
Co-Authored-By:
Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017KPzWwuTEeX2vXGXvyGn4q
tdgiang authored -
createTransaction now calls SobanleClient.createAutoOrder(customerData, targetAmount, cb) before writing an AdminTransaction. The transaction's amount is set from the real Sổ Bán Lẻ order total (order.orderTotal) rather than the admin's raw input amount, and sobanleOrderId is stored for reconciliation. On Sổ Bán Lẻ failure, returns 502 {code:"99", data:"SOBANLE_ORDER_FAILED"} with no DB row written. The success response contract ({code:"00", data:{merTrxId, paymentUrl}}) is unchanged. Co-Authored-By:Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017KPzWwuTEeX2vXGXvyGn4q
tdgiang authored -
Co-Authored-By:
Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017KPzWwuTEeX2vXGXvyGn4q
tdgiang authored -
Orchestrates product selection via productComboPicker and sale creation, failing gracefully if no product combo matches the target amount. Co-Authored-By:
Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017KPzWwuTEeX2vXGXvyGn4q
tdgiang authored -
tdgiang authored
-
Two rulings made during Task 4 execution (see ledger): the early-refresh headroom now scales with token TTL instead of a flat 60s (a flat skew made the test's short-TTL token already "expired" the instant it was cached), and every scratch script that requires config/config.js now sets NODE_ENV before requiring it (the app's real entrypoint always resolves this via config/init first; the plan's scratch scripts had bypassed that). Co-Authored-By:
Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017KPzWwuTEeX2vXGXvyGn4q
tdgiang authored -
tdgiang authored
-
Co-Authored-By:
Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017KPzWwuTEeX2vXGXvyGn4q
tdgiang authored -
tdgiang authored
-
tdgiang authored
-
Co-Authored-By:
Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017KPzWwuTEeX2vXGXvyGn4q
tdgiang authored -
Adds GET /admin index redirect, createdAt date-range filtering on the transactions list with a stat strip (total/success/pending/revenue) and client-side search, plus inline validation and quick amount chips on the new-transaction form. Co-Authored-By:
Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017KPzWwuTEeX2vXGXvyGn4q
tdgiang authored
-
- 30 Aug, 2026 4 commits
-
-
Trust the single nginx reverse-proxy hop (confirmed in deploy.md) so express-rate-limit's per-client bucketing on POST /admin/login sees the real client IP instead of nginx's. Also fix a dead cookieSecure check (NODE_ENV === 'secure' never matched) so the session cookie actually gets Secure in production, verified live with X-Forwarded-Proto: https. Co-Authored-By:Claude Sonnet 5 <noreply@anthropic.com>
tdgiang authored -
# Conflicts: # app/views/admin/transactions-list.server.view.html # app/views/admin/transactions-new.server.view.html
tdgiang authored -
Applies a cohesive visual system to the admin panel: a navy/blue dashboard style (Inter font) for the internal staff pages, and a navy/gold fintech-trust style (IBM Plex Sans) for the customer-facing payment pages. Preserves all existing form fields, MegaPay integration markup, and Swig template bindings unchanged.
tdgiang authored -
Async bcryptjs measured to still not yield the event loop for realistic hash costs (~70ms compares finish before its 100ms yield threshold), so a login flood could still stall the same process's live MegaPay payment webhooks. Caps each client (or shared-proxy-bucket, see known limitation below) to 5 POST /admin/login attempts per rolling 60s window via express-rate-limit; requests over the limit get a 429 with a Vietnamese error and never reach adminAuth.login, so the bcrypt compare never runs. Known limitation: this app has no app.set('trust proxy', ...) configured and runs behind a reverse proxy in production, so express-rate-limit's default req.ip-based bucketing will key off the proxy's address, not the real client IP. In production this enforces "5 attempts/minute in aggregate behind the proxy" rather than "5 per real client IP" — an accepted trade-off for this low-traffic internal tool, but not the same guarantee trust proxy + per-IP limiting would give. Configuring trust proxy is an infrastructure change, left out of scope here.tdgiang authored
-
- 29 Aug, 2026 13 commits
-
-
tdgiang authored
-
Co-Authored-By:Claude Sonnet 5 <noreply@anthropic.com>
tdgiang authored -
tdgiang authored
-
tdgiang authored
-
Appends accountsList, createAccount, toggleAccount, and resetAccountPassword to adminAuth.server.controller.js, adds the accounts-list view, and wires the /admin/accounts* routes behind requireLogin + requireAdmin. Co-Authored-By:Claude Sonnet 5 <noreply@anthropic.com>
tdgiang authored -
createTransaction now stamps createdByUsername from the session. listTransactions scopes results to the logged-in staff's own transactions, or (for admins) all transactions with an optional ?staff= filter sourced from real AdminUser records - the filter is ignored for non-admin sessions so staff cannot view another account's data by editing the query string. Transaction routes now use requireLogin instead of basicAuth, which is now unused and deleted. Co-Authored-By:Claude Sonnet 5 <noreply@anthropic.com>
tdgiang authored -
Co-Authored-By:Claude Sonnet 5 <noreply@anthropic.com>
tdgiang authored -
Adds AdminTransaction.createdByUsername (default null) so future permission-scoped views can attribute data to a user, plus a new app/libs/adminBootstrap.js that seeds the first admin account from ADMIN_USER/ADMIN_PASSWORD on boot and backfills any pre-existing transaction lacking createdByUsername to that admin. Wired into server.js's mongoose 'open' handler. Idempotent across repeated boots. Co-Authored-By:Claude Sonnet 5 <noreply@anthropic.com>
tdgiang authored -
Implement two authentication and authorization middlewares: - requireLogin: checks req.session.userId and responds with 302 redirect to /admin/login for HTML requests, or 401 JSON for API requests - requireAdmin: checks req.session.role === "admin" and responds with 403 for unauthorized access Co-Authored-By:Claude Sonnet 5 <noreply@anthropic.com>
tdgiang authored -
Add passwordHash utility module wrapping bcryptjs for secure password hashing, and AdminUser Mongoose model with username (unique, lowercase), passwordHash, role (admin/staff), and active fields. Co-Authored-By:Claude Sonnet 5 <noreply@anthropic.com>
tdgiang authored -
tdgiang authored
-
tdgiang authored
-
# Conflicts: # .gitignore
tdgiang authored
-
- 28 Aug, 2026 10 commits
-
-
tdgiang authored
-
Adds listTransactions controller action and its Swig view, and creates app/routes/admin.server.routes.js to finally wire every admin controller function (Tasks 7-9) to real HTTP routes via the app's glob-based route loader. basicAuth protects /admin/transactions and /admin/transactions/new; the pay page, epay return, and epay IPN endpoints stay public. Co-Authored-By:Claude Sonnet 5 <noreply@anthropic.com>
tdgiang authored -
tdgiang authored
-
Co-Authored-By:Claude Sonnet 5 <noreply@anthropic.com>
tdgiang authored -
Implements Task 7: newTransactionForm and createTransaction controller functions with supporting view. - Controller generates merTrxId and transCode with UUID-based unique suffix - Signs transaction using epaySign.signRequest - Persists AdminTransaction record to MongoDB - Returns JSON response with code 00/99 and payment URL - View provides HTML form to submit customer data and display payment link Co-Authored-By:Claude Sonnet 5 <noreply@anthropic.com>
tdgiang authored -
Co-Authored-By:Claude Sonnet 5 <noreply@anthropic.com>
tdgiang authored -
Co-Authored-By:Claude Sonnet 5 <noreply@anthropic.com>
tdgiang authored -
Implements signRequest and signResponse functions that compute MegaPay/Epay HMAC-SHA256 signatures per MGP_Merchant_Interface specification sections 5.1 and 5.3. These functions will be consumed by Tasks 7 and 9 of the admin transaction management feature. Co-Authored-By:Claude Sonnet 5 <noreply@anthropic.com>
tdgiang authored -
tdgiang authored
-
Add mongoUri and admin.{user,password} config keys to config/env/all.js for Task 2 admin transaction management feature. Follows existing env var pattern used for payment provider credentials. Default mongoUri points to Docker service; admin credentials read from ADMIN_USER/ADMIN_PASSWORD env. Co-Authored-By:Claude Sonnet 5 <noreply@anthropic.com>
tdgiang authored
-