1. 03 Sep, 2026 3 commits
  2. 02 Sep, 2026 3 commits
    • Verify status and amount fields on epay IPN, not just resultCd · 9df1644b
      Per MGP_Merchant_Interface_V1.4.7VN.md section 5.3.2: resultCd "00_000"
      alone does not mean "payment success" - MGP can send it for a refund
      IPN too (status field: "0" = payment, "2" = refund). epayIPN previously
      set status="success" purely off resultCd, which would have misclassified
      a refund event on a still-pending transaction. Now requires resultCd
      "00_000" AND status "0".
      
      Also adds an explicit amount cross-check between the IPN body and the
      stored transaction (spec's "Lưu ý đặc biệt 2") as a defense-in-depth,
      clearly-diagnosable guard alongside the existing signature verification
      (which already implicitly enforces this, but with a generic error).
      Co-Authored-By: 's avatarClaude Sonnet 5 <noreply@anthropic.com>
      Claude-Session: https://claude.ai/code/session_017KPzWwuTEeX2vXGXvyGn4q
      tdgiang authored
    • Cap product combo qty by real stock instead of a fixed 3 per line · a970a2ac
      Live E2E testing against the real Sổ Bán Lẻ catalog (64 usable
      products, avg price ~24k) showed the fixed maxQtyPerLine=3 capped the
      achievable order total at ~4.86M regardless of retry count - a
      structural ceiling, not bad luck. 7/10 test amounts above ~4.2M failed
      with NO_PRODUCT_COMBO_MATCH every time.
      
      Default per-line qty is now bounded only by the product's actual stock
      (previously min(3, qty), now just qty), raising the real ceiling to
      ~2B on the current catalog. maxQtyPerLine is still available as an
      explicit override for callers that want a cap.
      
      Verified against the real catalog: all 7 previously-failing amounts
      (4.2M-19.5M) now succeed, real order totals landing within the
      50,000 tolerance band every time.
      Co-Authored-By: 's avatarClaude Sonnet 5 <noreply@anthropic.com>
      Claude-Session: https://claude.ai/code/session_017KPzWwuTEeX2vXGXvyGn4q
      tdgiang authored
    • Require CMND/CCCD on transaction creation and enforce amount range 1M-20M · f19658e1
      Sổ Bán Lẻ rejects customer creation with a 422 ("Vui lòng nhập số
      CMND/CCCD") when id_card_number is null - discovered via manual E2E
      testing against the real service. Add a required customerIdCard field to
      the admin form and controller, persisted on AdminTransaction, and pass
      through as customer_data.id_card_number instead of null.
      
      Also add the requested amount validation: payment amount must be
      strictly greater than 1,000,000 and less than 20,000,000, enforced both
      client-side (form) and server-side (createTransaction).
      Co-Authored-By: 's avatarClaude Sonnet 5 <noreply@anthropic.com>
      Claude-Session: https://claude.ai/code/session_017KPzWwuTEeX2vXGXvyGn4q
      tdgiang authored
  3. 31 Aug, 2026 13 commits
  4. 30 Aug, 2026 4 commits
    • Configure trust proxy and enable Secure session cookies in production · de551724
      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: 's avatarClaude Sonnet 5 <noreply@anthropic.com>
      tdgiang authored
    • Merge branch 'feature/admin-account-management' into dev · ddd32d38
      # Conflicts:
      #	app/views/admin/transactions-list.server.view.html
      #	app/views/admin/transactions-new.server.view.html
      tdgiang authored
    • Redesign admin transaction management UI · 40388425
      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
    • Rate-limit POST /admin/login to bound bcrypt-induced event-loop stalls · 4bb319e9
      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
  5. 29 Aug, 2026 13 commits
  6. 28 Aug, 2026 4 commits