EPaySe

changelog.title

v2.1.0

changelog.subtitle

changelog.types.security
changelog.categories.platform

Fix AUTH-VULN-01: replace TRUSTED_PROXIES=* with auto-refreshed Cloudflare CIDRs

TRUSTED_PROXIES=* allowed any client to forge X-Forwarded-For and bypass IP-keyed rate limiters (login, 2FA, forgot-password). Implemented cloudflare:refresh-ips Artisan command that fetches Cloudflare public IP ranges daily and caches them (Cache::forever infrastructure:trusted_proxies). bootstrap/app.php now reads from cache with a priority chain: env override → cache → hardcoded fallback (fail-closed, never * in staging/production). Scheduled daily with withoutOverlapping(5). AWS_VPC_CIDR env var appends internal proxy network CIDR (required for Dokploy/Docker where Traefik IP is the REMOTE_ADDR, not CF IP directly).

changelog.types.fix
changelog.categories.platform

Surface container startup migration errors (Dokploy deploy debuggability)

Container entrypoint script was silently exiting in ~1.3s when Laravel migrate:status failed (e.g. phpdotenv parse error from unquoted .env values), because the `MIGRATE_STATUS=$(...)` assignment under `set -e` killed bash before the error-handler block could print the captured output. Wrapped the capture in `set +e`/`set -e` so failures now surface in container logs with the most common causes documented (phpdotenv parse error / DB unreachable / missing APP_KEY). Eliminates a major class of black-box Dokploy rollbacks.

changelog.types.fix
changelog.categories.platform

Document ALERT_TELEGRAM_WEBHOOK_SECRET in env templates

Both .env.staging.example and .env.production.example were missing the ALERT_TELEGRAM_WEBHOOK_SECRET placeholder needed for interactive alert buttons (Snooze/Mute/Acknowledge). Anyone provisioning a fresh VPS following the template silently ended up with non-functional buttons. Added the placeholder with inline instructions for generating the secret and registering the webhook.

changelog.types.fix
changelog.categories.platform

Fix interactive Telegram alert buttons (Snooze/Mute/Acknowledge) not persisting

Chargeback Telegram alerts kept re-firing every hour even after ops clicked the inline keyboard buttons because Telegram was never delivering callback_query updates. Two compounding bugs: (1) merchant bot webhook subscribed only to `message` updates when registered before the interactive alert feature shipped, so Telegram silently dropped button clicks; (2) telegram:set-alert-webhook printed SUCCESS in same-token mode without re-registering anything, hiding the misconfig. Fix: command now delegates to TelegramBotService::setWebhook in same-token mode so callback_query is subscribed when alerts.interactive.enabled=true; new telegram:webhook-status read-only health command surfaces the misconfig with non-zero exit; webhook controller now logs each callback_query received.

changelog.types.fix
changelog.categories.platform

Fix stale merchant balance shown in admin, reports and AI assistant

Merchant balance is now always derived from fund_flows (the single source of truth) instead of a deprecated, never-updated merchants.balance column. The admin merchant list/detail/analytics, KYB review, the daily Telegram report, the AI balance tool and the merchants export previously showed a stale or zero balance; they now reflect the real available balance per the merchant's default currency. The legacy column has been dropped.

changelog.types.fix
changelog.categories.dashboard

Fix retry-limits settings Save doing nothing

The Transaction Config → Retry Limits form posted to a non-existent route, so clicking Save Changes silently failed and merchants could not change their max retry attempts or cooldown period. The form now targets the correct route and saves persist.

changelog.types.fix
changelog.categories.payment

Unify card brand detection to single canonical service

Introduced CardBrandDetector as single source of truth for card brand detection, replacing 5 parallel implementations with divergent bugs. Fixes two production issues: (1) CardBrandEnum::UNKNOWN fatal Error at PaymentController:2446 causing runtime crash for JCB/Discover/UnionPay/Diners cards; (2) 'union-pay' vs 'unionpay' mismatch in BaseGateway causing UnionPay transactions to be stored as 'other'. Detection now covers all 7 brands with correct ISO 7812 BIN ranges including Mastercard 2-series (2221–2720, excluding Mir), JCB legacy BINs (2131xx, 1800xx), and CUP/Discover co-brand resolution via Discover rails.

changelog.types.security
changelog.categories.security

Encrypt API key secrets and webhook tokens at rest (A.4)

Added Laravel `encrypted` cast to ApiKey.secret_key, ApiKey.previous_secret_keys (encrypted:array), and WebhookConfiguration.secret_token. Data migration widens columns to TEXT then encrypts all existing plaintext values via APP_KEY (AES-256-CBC). Prevents secret exposure if DB dump is leaked.

changelog.types.security
changelog.categories.security

Post-Shannon R4 security expansion: 8 additional findings fixed

P0: PinddPayGateway webhook signature bypass — replaced AND logic (invalid-sig && failed-requery) with immediate rejection on invalid signature. P0/P1: AirWallexGateway and JPayGateway (deprecated) now throw RuntimeException on verifyWebhookSignature to prevent unverified webhook processing. P1: ArticleController bulk-action delete — moved checkAdminPermission outside try-catch so abort(403) propagates correctly instead of being swallowed as a redirect. P2: UpdateWebsiteRequest missing NoSsrfUrl rule — update path now matches store path SSRF + HTTPS enforcement. P2: Sandbox/PaymentForm.vue and CardPaymentForm.vue postMessage handlers missing origin check — added window.location.origin guard. P3: Deleted 3 unrouted dead-code methods in PaymentFormSimulatorController with unsafe transaction/refund ownership patterns. P3: Created config/security.php to wire up force_skip_ip_check and force_ssrf_validation env overrides previously referencing non-existent config file.

changelog.types.security
changelog.categories.security

Fix 7 security findings from Shannon R4 pentest (P0-P2)

P0: Signed 3DS callback URLs (AUTHZ-VULN-04) — SandboxGateway now generates two pre-signed success/fail callback URLs; ThreeDSecureController rejects unsigned simulate param with 403. P0: SSRF guard on PSP credentials (SSRF-VULN-02) — NoSsrfUrl validation added to base_url/base_url_post/base_url_get in all three PSP create/update/credentials methods. P0: IDOR ownership check in PaymentFormSimulatorController — prevents cross-merchant API key exposure by verifying authenticated user's merchant matches requested merchantId. P1: Fixed postMessage origin allowlist logic in Checkout/Index.vue — empty allowedOrigins no longer silently passes all origins. P1: Fixed wrong config key subdomains.envs.→domains. in PaymentController allowedOrigins builder. P1: GenioPago verifyWebhookSignature throws RuntimeException (presence-only check was not real verification). P2: Removed 'staging' from HMAC IP-whitelist skip list; replace with config('security.force_skip_ip_check') flag for explicit overrides.

changelog.types.fix
changelog.categories.platform

Fix BroadcastException when Reverb is not deployed on dedicated host

TransactionCreated and TransactionUpdated events were failing as queue jobs on staging because REVERB_HOST pointed to the web app domain. Added broadcastIf() guard: skips broadcasting when REVERB_HOST equals the app hostname, preventing BroadcastException and queue job failures until Reverb is deployed on its own server.

changelog.types.fix
changelog.categories.security

Fix SecurityMonitoring TypeError when User-Agent header is absent

isSimilarUserAgent() declared strict string parameters but $request->userAgent() returns ?string. Automated tools (curl, scanners) sending requests with no UA header caused TypeError. Fixed by adding null guards in detectUserAgentChange(): skip comparison and skip cache update when currentUserAgent is null. Added 3 regression tests covering null UA, cache preservation, and first-request UA caching.

changelog.types.security
changelog.categories.security

Patch Symfony CVEs (symfony/http-kernel, mailer, mime, routing) + fix ZAP CI pipeline

Upgraded 10 symfony/* packages to v8.0.12 to address 5 CVEs released 2026-05-20 (CVE-2025-XXXX series). Fixed ZAP Automation Framework CI: corrected script engine from ECMAScript→Groovy in api-v1.yaml; updated deprecated formBased→form and cookieBased→cookie method names in authenticated-merchant.yaml and authenticated-admin.yaml. Rewrote staging E2E resilient-request helper to use page.goto() (Chromium) instead of playwright.request (undici) to avoid 16KB response-header overflow from Vite's Link preload header. Updated security spec fixtures accordingly.

changelog.types.fix
changelog.categories.security

Fix Merchant\WebsiteController::store() 500 + scope api_key_id to current merchant

Phase 4 plural endpoint POST /websites was missing api_key_id in inline $request->validate() rules, causing NOT NULL DB constraint to throw QueryException as unhandled 500 on every call (3× repeat alerts on 2026-05-19 from manual SSRF probe). Added required + Rule::exists('api_keys','id')->where('merchant_id', $merchant->id) validation — closes cross-merchant API key binding gap as defense-in-depth. Also wrapped WebsiteService::addDomain() call in try-catch (mirroring sibling removeDomain pattern) so the 5-changes-per-30-days rate-limit raw \Exception now renders as field error via back()->withErrors() instead of bubbling to exception reporter.

changelog.types.security
changelog.categories.security

OWASP ZAP Automation Framework — 5-layer security scanning

Replaces single-subdomain zap-baseline.py passive scan with 5 specialized CI jobs: passive (all 6 subdomains), active (public surface monthly), authenticated admin, authenticated merchant, HMAC-signed API v1. Adds ZapIntegrationTest.php with 10 permanent regression tests (SEC-101..SEC-110) mapping ZAP alert IDs to PHPUnit assertions. HmacSigner.groovy signs /api/v1/* requests after active-scan body mutations. Graduated blocking: advisory (Day 0-30) → passive blocks staging deploy (Day 31+) → API blocks (Day 61+). Removes critical /debug-500 route that leaked PHP version and env info.

changelog.types.breaking
changelog.categories.payment

Tier-based fee configuration replaces per-(merchant, payment_method) JSON fees

BREAKING: Replaces per-(merchant × PM) JSON fee config with discrete tier templates (Standard/Premium/HighVolume/Enterprise) + per-(PM × currency) provider costs. Onboarding picks 1 tier instead of 5 JSON columns × N PMs. Schema drops merchant_method.{payment,refund,dispute,claim,protest}_fees, api_key_method.{payment,refund,dispute,protest}_fees, payment_methods.default_*_fees, global_fee_configs table. Adds merchant_fee_tiers, merchant_fee_tier_rates, payment_method_provider_costs (+ history tables for Decision #5 Option 3). FeeConfigResolver service replaces WithMerchantMethodFee trait. Decision #6 Option B fallback to tier code='standard' when merchant.tier_id NULL. New routes: /operate/fee-tiers CRUD, /operate/merchants/{id}/fee-tier (audit log). 14 commits, net -2329 lines.

changelog.types.fix
changelog.categories.dashboard

Fix 5 admin UI bugs — i18n, MobileCardList, currency, CaptchaConfig, error pages

CI-045: Contact form submit button was hardcoded English, now uses i18n key. CI-040: MobileCardList on 5 admin pages (Collusion, FraudSignals, DeviceFingerprints, Customers, FundFlows) was receiving :data= instead of :items= causing blank mobile cards. CI-034: Marketing Costs table displayed all amounts as USD instead of the row currency. CI-041: CaptchaConfig admin pages had 22 wrong i18n key paths (missing 18 translation keys); DataTable was missing selection-change emit. CI-038: 403/404 error pages redesigned — full dark mode, ApplicationLogo, DarkModeToggle, permission detail block, i18n, matching gradient theme.

changelog.types.improvement
changelog.categories.api

Rename X-Client-ID HMAC header to X-Api-Key-Id

BREAKING: The HMAC authentication header X-Client-ID has been renamed to X-Api-Key-Id across all environments (middleware, signature service, simulator, docs pages, E2E tests). Also completes the ApiClient→ApiKey rename refactor: renamed test directories (ApiClients/→ApiKeys/, ApiClientTable/→ApiKeyTable/), fixed cache keys (api_client_last_used→api_key_last_used, cascade_config:api_client→cascade_config:api_key), updated query param (api_client_filter→api_key_filter), and fixed a silent cache invalidation bug in CascadingPaymentService where the forget() path used the old key name.

changelog.types.fix
changelog.categories.dashboard

Fix KYB matrix mode — wire document upload, nav buttons, and review display end-to-end

4 bugs fixed in /kyb/create when a Business Category (e.g. Adult Content) is selected: (1) KybController::storeDocuments now processes dynamic industry document slots from DocumentMatrixService — previously all matrix-mode uploads were silently dropped; (2) Navigation buttons (Save Draft / Complete Application / Previous) now render in both matrix and legacy mode — they were inside the v-else block so invisible when matrix mode was active; (3) Step 5 Review now shows Country and Business Category names — fixed missing eager load + camelCase key mismatch (businessCategory → business_category); (4) Sidebar document progress is now matrix-aware via new useKybDocumentMatrixProgress composable that groups sections by personal identity + required/optional industry slots. Also: form correctly restores uploaded matrix files on Step 4 revisit (buildKybDocs extended); document_type_code column populated for admin/reporting queries; Zod schema + transformValuesForSubmit extended for dynamic slots; PERSONAL_IDENTITY_CODES const exposed as single source-of-truth from PHP via Inertia prop.

changelog.types.fix
changelog.categories.platform

Fix Docker production build — switch to debian:bookworm + packages.sury.org

Launchpad PPA (ppa.launchpadcontent.net:443) is blocked by RackNerd VPS provider, causing build:docker to timeout after 400s when installing PHP 8.4 packages. Fixed by switching Dockerfile.production base from ubuntu:24.04 to debian:bookworm-slim and PHP source from the blocked Launchpad PPA to packages.sury.org/php/bookworm (Ondrej Surý's own server, confirmed accessible). PostgreSQL repo updated to bookworm-pgdg. GPG key saved directly (binary format, no gpg --dearmor). Build time: 594s end-to-end.

changelog.types.fix
changelog.categories.platform

Eliminate 30s Redis exception window on Swarm rolling deploy

5 coordinated fixes eliminating 5-15 RedisException Telegram alerts per deploy: (1) Wait-for-Redis gate in entrypoint reads .env via grep (Dokploy shell env is empty) and blocks supervisord from starting horizon/reverb/scheduler until Redis overlay DNS converges; (2) Fix reverb.php REDIS_TIMEOUT cast bug (60→2.0s); (3) Add retry_interval=100ms to phpredis connections for sub-second blip absorption; (4) 3-attempt retry in CheckRedisHealth middleware; (5) Graduated readiness endpoint returns 200+degraded for Redis-only failures (prevents Swarm false-positive rollback) with database-backed counter alerting at 5 consecutive failures.

changelog.types.improvement
changelog.categories.dashboard

Unify KYB Business Category select with shadcn Select pattern (Years/Turnover)

Migrated /kyb/create Step 1 Business Category from custom Popover+Command to shadcn <Select>, matching Years in Business / Annual Turnover styling (h-12, border-2, rounded-xl, focus:border-blue-500, full dark mode). Added Required + Locked badges and proper vee-validate componentField binding. Side-fixes 2 hidden bugs: (1) kyb.field_locked i18n key was missing from en/vi settings.json — 5 KYB fields rendered literal 'settings.kyb.field_locked' text in refill mode because vue-i18n v9 t(key, fallback) does NOT accept a fallback string; (2) KybController eager-loads businessCategory relation, so props.kyb.business_category arrives as an OBJECT not a code string — the old select compared object to string and silently failed to restore the selected category after reload. New resetMerchantKybState() helper in kyb-helpers.js resets ACTIVE merchant fixture to REGISTERED + clears Kyb in beforeEach. E2E suite kyb-industry-category.spec.js: 5/5 PASS.

changelog.types.security
changelog.categories.security

Patch HIGH severity vulnerabilities in phpoffice/phpspreadsheet (CVE-2026-34084 + 4 others)

Bumped phpoffice/phpspreadsheet from 1.30.2 to 1.30.4 (transitive dependency of maatwebsite/excel ^1.30.0). Fixes 5 advisories: CVE-2026-34084 SSRF/RCE in IOFactory::load (HIGH), CVE-2026-40902 + CVE-2026-40863 (HIGH), CVE-2026-40296 + CVE-2026-35453 XSS in HTML writer (medium). composer audit now clean. Also bumped 9 transitive dependencies (symfony/* 1.33→1.37, webmozart/assert 2.1→2.3, etc.) within minor version constraints — no breaking changes expected.

changelog.types.fix
changelog.categories.payment

Prevent PSP circuit breaker resonance loop on mass-seeded transactions

MassTransactionSeeder PENDING transactions (5% x 10M = 500K) with fake provider_id triggered ProcessTransactionExpire every 5 min, calling real PinddPay -> 5 errors/60s tripped circuit breaker (300s cooldown ≈ 5min scheduler = resonance loop). Three-part fix: (1) MassDataGenerator removes PENDING from status distribution; (2) PinddPayGateway returns isNotFound flag for 404/NOT_FOUND responses, distinct from real PSP errors; (3) PspStatusQueryService bypasses recordCircuitFailure() when PSP confirms transaction not found. Added staging:cleanup-mass-seed-pending artisan command for stuck data cleanup.

changelog.types.fix
changelog.categories.platform

Fix 500 error on KYB pages for newly registered merchants

Fixed enum vs string comparison in SettingController (statusColor always returned null), added null-safe operator for firstRegisteredEmail, fixed optional chaining in Index.vue. Added idempotent recovery migration to apply missed April 19 KYB structures if business_categories table is missing on staging. Added BusinessCategorySeeder to FoundationSeeder so reference data is always seeded.

changelog.types.deprecated
changelog.categories.platform

Remove redundant sandbox merchant test pages

Removed /sandbox/test-payment and /sandbox/testing merchant portal pages. These pages duplicated functionality already available through the checkout sandbox flow with the Sandbox PSP.

changelog.types.feature
changelog.categories.payment

Cascade deferred items: timeline, health dashboard, dispute attribution, QA seeder

U10: decline_code select in cascade error rule forms. D14: CascadeChainSeeder with 3 exemplar chains for QA. F5/X3: disputes.payment_attempt_id FK for cascade-aware chargeback attribution. U11: CascadeAttemptsTimeline Vue component in merchant transaction detail. O9: /operate/cascade/health admin dashboard with PSP pickup rates, exhaustion merchants, and hourly volume chart.

changelog.types.security
changelog.categories.payment

PCI-safe PSP payload sanitization in all gateways

sanitizeErrorBody() and sanitizePspArray() helpers moved to BaseGateway — all gateways now inherit PAN/CVV redaction for Log::* calls. mapDeclineCode() docblock mandates sanitization before logging PSP responses. Removed duplicate implementation from GenioPagoGateway.

changelog.types.fix
changelog.categories.payment

Webhook concurrent race: lockForUpdate returns fresh Transaction

claimWebhookToken() now returns the freshly-locked Transaction from inside DB::transaction(), preventing stale in-memory reads on status, paid_amount, and refunded_amount after acquiring the row lock. All three callers (processPaymentAttempt, processRefund, processDispute) now reassign $transaction to the fresh instance, closing the concurrent webhook double-processing gap.

changelog.types.improvement
changelog.categories.payment

Cascade contract CI enforcement for all gateway implementations

AllGatewaysImplementCascadeContractTest auto-discovers every concrete gateway via glob() and uses PHP Reflection to verify mapDeclineCode() and getCascadeCapabilities() are overridden — not just inherited from BaseGateway. Fail prevents any gateway from deploying without explicit cascade mappings.

changelog.types.improvement
changelog.categories.payment

Cascade gateway capability gating and PSP rate protection

CascadeCapabilities DTO now derives gateway capability (S2S, hosted, async-webhook-only) from existing interface contracts — single source of truth, no array drift. RoutingValidator::canHandleCascadeContext blocks S2S-only PSPs from hosted cascade and async-webhook-only PSPs from S2S cascade. System-level kill switches (CASCADE_ENABLED, CASCADE_HOSTED_ENABLED, CASCADE_S2S_ENABLED) gate all cascade flows at shouldCascade() for instant ops rollback. PSP outbound rate bucket (Laravel RateLimiter, 1s decay, configurable per-PSP RPS cap) prevents cascade storms from self-DDoS-ing backup PSPs.

changelog.types.feature
changelog.categories.api

S2S idempotency key middleware (X-Idempotency-Key)

Merchants can now safely retry POST /api/v1/transaction/create-s2s by sending an X-Idempotency-Key header (32-64 chars). The middleware caches the first response in Redis (30-min TTL, namespaced per merchant) and replays it byte-identical on retry. A request fingerprint (amount + currency + ref + card_last4) guards against key reuse with mutated payload (returns 422 IDEMPOTENCY_KEY_CONFLICT). PENDING_3DS responses are not cached. Cache failures fail closed with HTTP 503. The key is persisted on the hop-0 payment_attempt and forwarded along the cascade chain by the orchestrator. Gated behind payment.cascade.idempotency_enabled (default ON).

changelog.types.feature
changelog.categories.platform

Geographic restrictions for payment methods (admin-only)

Admins can now define allowed/blocked countries per payment method with allow/block mode toggle. Routing engine enforces country at method level using billing country. BIN country enrichment (8-digit) and IP geolocation add additional fraud signals. Routing rule builder hidden from merchant portal — managed centrally by admin team. Cascade and fallback paths both enforce country restrictions.

changelog.types.security
changelog.categories.security

OFAC sanctioned countries hard block (non-disableable)

Sanctioned countries (CU, IR, KP, SY, BY, MM) are hard-blocked at the routing layer, active regardless of admin allowlist configuration or feature flag state. Admin UI rejects sanctioned countries in allowlist mode with AlertDispatcher critical alert and distinct AdminActivity audit event (payment_method.sanctioned_country_allow_attempt). Legal counsel sign-off required before changes per RB-006.

changelog.types.feature
changelog.categories.fraud

BIN country enrichment for routing context

RoutingContext now includes bin_country sourced from BIN lookup (8-digit BIN support for Visa/MC 2022 rollout). Disagreement between billing, IP, and BIN country sources emits COUNTRY_SOURCE_MISMATCH fraud signal asynchronously via queue. Prepaid cards and sandbox PSP skip the signal to avoid false positives. Single-flight Cache::lock prevents external API stampede on cache miss.

changelog.types.fix
changelog.categories.platform

Cache invalidation back-fill for payment method config updates

Back-fill Cache::forget('payment_method:{id}') into updateCardBrands() (pre-existing bug — 1h stale config after admin update). Optimistic locking (last_updated_at check) prevents concurrent admin overwrite with 409 Conflict response. CountryCodeNormalizer added for Kosovo/disputed territory ISO code normalization.

changelog.types.feature
changelog.categories.payment

Sweeper for stuck 3DS authenticating transactions

A scheduled job runs every 5 minutes to mark transactions stuck in AUTHENTICATING status (no 3DS callback received within 15 minutes) as failed and fire the merchant failure webhook. The 3DS callback handler now silently no-ops if the attempt has already been swept, preventing split-brain races. Gated behind payment.cascade.sweeper_enabled.

changelog.types.feature
changelog.categories.payment

S2S synchronous cascade orchestrator (Phase 3)

Server-to-server payments now cascade synchronously across configured PSP fallbacks within a single HTTP request. The orchestrator enforces a 25-second wall-time budget, marks the prior attempt FAILED before each hop so late webhooks are rejected as stale, performs lineage and event writes in a single short DB transaction, and decorates the response with opaque per-hop psp_codes and a cascade_group_id only when more than one hop ran. Single-attempt requests keep the legacy response shape (additive-only). Gated behind payment.cascade.enabled and payment.cascade.s2s_enabled.

changelog.types.security
changelog.categories.security

Redirect Proxy hardening — block page no longer leaks transaction metadata

Flagged bots now receive a generic Transaction Processed page with zero transaction metadata. Amount, currency, status, and description are no longer exposed to detected crawlers, satisfying the minimum-disclosure principle of the proxy layer.

changelog.types.improvement
changelog.categories.security

Redirect Proxy ops alert on Redis fallback

When Redis is unavailable and the proxy token generator fails, EPaySe now raises a Telegram alert in addition to the error log so operators learn about proxy degradation immediately. The alert is dispatched after the surrounding DB::commit() via DB::afterCommit() so the HTTP call never holds an open database transaction. Payment flow continues uninterrupted via the existing fail-open fallback.

changelog.types.improvement
changelog.categories.platform

Redirect Proxy — psp_domain exposed to gateways

Added BaseGateway::getMerchantDomain() helper so PSP adapters that support a merchant_domain field can read the merchant's configured PSP Domain. Admin UI labels updated to reflect that the field is opt-in per PSP.

changelog.types.fix
changelog.categories.dashboard

DataTable component — opt-in default sort column

The shared DataTable.vue component previously hardcoded { id: 'created_at', desc: true } as its initial TanStack sorting state, causing '[Table] Column with id created_at does not exist' console warnings on tables that use a different timestamp column (e.g. updated_at). DataTable now accepts a defaultSort prop; the Redirect Proxy admin list opts in with updated_at. Backward compatible — existing consumers keep the created_at default.

changelog.types.improvement
changelog.categories.dashboard

Industry-driven KYB form fully activated

Removed feature flag and enabled industry-specific document requirements for all merchants. Fixed security gap where industry form branch lacked file validation.

changelog.types.feature
changelog.categories.dashboard

Merchant self-service cascade error rules

Merchants can now create, edit, and manage error pattern rules that control PSP cascade behavior from the Transaction Config dashboard. Merchant rules take highest priority in the evaluation chain: merchant-specific → admin PSP-specific → admin global. Regex match type is restricted to admin-only for security.

changelog.types.feature
changelog.categories.dashboard

Redesigned KYB wizard with resume-by-email

KYB onboarding is now a 5-step wizard with real-time progress save, email magic-link resume, mobile camera capture for documents, and a review page with e-signature attestation before final submission.

changelog.types.improvement
changelog.categories.dashboard

Industry-specific KYB document requirements

KYB form now adapts to your business category — only relevant documents are requested, reducing onboarding time for low-risk industries. High-risk industries receive clear guidance on required compliance documents.

changelog.types.fix
changelog.categories.payment

Financial precision: withdrawal fee calculation — bcmath chain (session 14)

WithdrawalController::store() computed withdrawals.total_fee using native PHP arithmetic operators (+, *, /) on amounts from DB (string numeric) and config (integer), causing silent float coercion before DB write to numeric(36,18) column. Fix: cast $amount to string at extraction point, replace all fee arithmetic with bcmul/bcdiv/bcadd chain, replace round($totalFee, 2) with bcadd($total, '0', 2). Also added explicit (string) cast for fee_amount DB write. Final comprehensive scan of all app/ directories (Traits, Observers, Listeners, Jobs, Commands, Models) confirmed 0 additional CRITICAL violations — CAST-AS-TEXT audit exhaustively complete across 14 sessions.

changelog.types.fix
changelog.categories.payment

Financial precision: gateway webhook chain — WebhookDTO + BaseGateway + all PSPs (sessions 9-10)

Closed final float precision leak in gateway webhook processing chain. Root cause: WebhookDTO::$amount was typed as float, causing PHP to coerce string amounts from PSP JSON payloads before they reached any bcmath code. Chain: PSP JSON → parseWebhookData() → WebhookDTO(float $amount) → BaseGateway::processPaymentAttempt(float $paidAmount) → payment_attempts.paid_amount (DB write). Fix: WebhookDTO::$amount: string, BaseGateway::processPaymentAttempt(string $paidAmount), all 10 gateway parser call sites use (string) cast. Also fixed: DuplicatePaymentAlertService float subtraction → bcmath abs+bccomp; ProcessSandboxWebhook (float) cast removed; IframeTemplateGateway/S2STemplateGateway/GenioPagoGateway (float) casts removed; AirWallexGateway/V2 round() → (string); JPayGateway/V2 round() → (string); PinddPay 4 violations including checkoutWebhook(float) type mismatch with base class. 48/48 tests pass.

changelog.types.fix
changelog.categories.payment

Financial precision: dispute fees, BuyerShield, manual payments, refund guards (session 8)

Extended CAST-AS-TEXT audit from DB-level sums to float parameter types and native arithmetic operators (+, -, *, /). 19 CRITICAL violations across 9 files: (1) DisputeCreationService::calculateReversalFees() — 7 native float ops → bcmath; chargeback_amount round() → bcsub; correct bcmath implementation already existed in PaymentFeeCalculator. (2) BuyerClaimResolutionService::calculateReversalFees() — same pattern, 7 float ops + (float) cast on resolved_amount. (3) FundFlowService::createCorrectionEntry() — float $correctedAmount → string; FundFlowController (float) cast → (string). (4) MarkupFeeService chargeMarkupFee/updateMarkupFee — float $amount → string; abs() < 0.01 → bccomp abs. (5) ReconciliationFundFlowService — abs((float) $pendingFlow->amount) → bcmath; float $amount → string. (6) ManualPaymentAttemptService — 4 methods, floatval() → (string), feeds paid_amount DB column. (7) BuyerClaimService — ?float $amount → ?string for claim_amount DB column. (8) BuyerClaimController (string) cast added. (9) BaseGateway::processRefund() — float $refundAmount → string.

changelog.types.fix
changelog.categories.platform

Financial precision: AI tool settlement sums (session 7)

GetSettlementData AI tool — two DB-level sums on settlements.net_amount and gross_amount fed into bcadd/bcdiv currency conversion chain. Replaced with CAST(COALESCE(SUM(amount), 0) AS TEXT) pattern. Exhaustive scan of all remaining ->sum() calls in app/ completed — remaining MEDIUM violations are display-only (number_format, analytics charts) and do not feed bcmath chains.

changelog.types.fix
changelog.categories.platform

Fix CI-035: reserves export 504 timeout — correlated subquery → LEFT JOIN

ReservesExport::buildOptimizedQuery() had a correlated subquery in the SELECT clause: (SELECT t.ref FROM transactions t WHERE t.id = mr.transaction_id LIMIT 1). With 218,592+ reserve rows, this executed once per row (N×1 queries), causing Gateway Timeout. Fixed by replacing with LEFT JOIN transactions t ON t.id = mr.transaction_id + t.ref as transaction_ref. The buildSearchCondition() EXISTS subquery (for search-only, not per-row) remains unchanged. 31/31 ReserveControllerTest pass.

changelog.types.fix
changelog.categories.platform

Fix CI-036: buyer claims export DISTINCT/ORDER BY PostgreSQL error

BuyerClaimsExport::buildOptimizedQuery() used SELECT DISTINCT without bc.created_at in the column list, but BaseExport::applySorting() adds ORDER BY bc.created_at DESC. PostgreSQL strict: all ORDER BY expressions must appear in SELECT list when using DISTINCT (unlike MySQL). Fixed by adding bc.created_at to the SELECT DISTINCT column list.

changelog.types.fix
changelog.categories.platform

Fix EG-021: self-host Inter/Figtree fonts via @fontsource, remove external CDN

Removed external font CDN references (fonts.bunny.net) from app.blade.php and admin.blade.php. Fonts are now bundled via @fontsource/inter and @fontsource/figtree npm packages imported in app.js and admin.js. Fixes: (1) CSP font-src violations when CDN is blocked, (2) Playwright browser_navigate timeout from 7-12s per-file CDN latency, (3) external dependency for font loading.

changelog.types.fix
changelog.categories.security

Fix EG-021b: add localhost:5173 to font-src CSP in Nginx conf for 3 subdomains

docs.epayse.local, gateway.epayse.local, and status.epayse.local use hardcoded Nginx-level CSP that bypassed Laravel middleware updates. Added http://localhost:5173 to font-src directive in all 3 Nginx conf files to allow Vite dev server fonts.

changelog.types.fix
changelog.categories.dashboard

Fix CI-033: StatsCarousel wrong prop name (:items → :stats) in Invoice/Index.vue

Invoice/Index.vue was passing :items='statsCards' to StatsCarousel component, but the component expects :stats prop. Fixed prop name.

changelog.types.fix
changelog.categories.dashboard

Fix CI-034: InvoiceTable default sort column created_at → invoice_number

InvoiceTable/DataTable.vue had initial sort { id: 'created_at', desc: true } but created_at is not a valid accessorKey in Invoice column definitions. Also Invoice/Index.vue had defaultField: 'created_at'. Both changed to invoice_number (valid accessorKey and allowed sort column in InvoiceController). Prevents TanStack Table sort error.

changelog.types.fix
changelog.categories.payment

Financial precision: float-to-string audit complete — 78+ violations fixed across 55 files

Sessions 7-12 of CAST-AS-TEXT audit. Expanded scope from ->sum() to float method signatures, native arithmetic operators, round(), and abs() patterns. (1) WebhookDTO::$amount: float → string — root of full precision chain: PSP JSON → WebhookDTO → BaseGateway::processPaymentAttempt(string) → paid_amount DB write. All 6 gateway parsers cast to string at parse site. (2) V1 gateway ghost violations — AirWallexGateway and JPayGateway createPayment() have own request_amount DB writes bypassing AbstractPaymentGateway base-class fix; JPayGateway/AirWallexGateway checkoutWebhook() also fixed. (3) ProtestFeeService::adjustProtestFee() — duplicated private float-arithmetic fee calculator replaced with bcmath; FundFlowService::createDisputeLossEntry(float) signature changed to string. (4) FundFlowController::store/manualPayout — (float) cast before createManualAdjustment() fund_flows DB write. (5) DisputeCreationService — chargebackAmount > 0 scalar comparison on bcmath string replaced with bccomp(). (6) BuyerClaimService, ManualPaymentAttemptService, BaseGateway::processRefund() — float signatures feeding DB writes. (7) InquirePendingTransactionStatus — (float) paid_amount before feeCalculator chain. (8) Test assertions updated for CAST AS TEXT string propagation to JSON responses. 424 tests pass.

changelog.types.fix
changelog.categories.payment

Financial precision: PSP gateway refund status and 8 additional bcmath violations fixed

Session 6 CAST-AS-TEXT audit. (1) 5 PSP gateways (AirWallex, AirWallexV2, JPay, JPayV2, ExamplePSP) used epsilon comparison abs(paid-total)<0.01 for refund status — replaced with bccomp(paid_amount, totalRefunded, 2) === 0. Bug: epsilon incorrectly marks $99.99 vs $100.00 payment as REFUND_SUCCESS. bccomp at scale=2 gives correct REFUND_PARTIAL_SUCCESS. (2) Dashboard TransactionController — refunds.amount sum + float arithmetic for isAllowRefund determination replaced with CAST AS TEXT + bcsub chain + bccomp comparison. (3) GetPlatformFinancialsData AI tool — 3 missed violations (lines 51,57,64) from session 2 audit: transactions and refunds CASE-SUM now CAST AS TEXT; full bcsub/bcmul/bcdiv chain for net revenue and rate calculations. (4) StatementGenerationService — all collection and DB sums replaced with bcadd reduce + CAST AS TEXT; netSettlement now uses bcsub chain. 45/45 tests pass.

changelog.types.fix
changelog.categories.platform

Financial precision: 7 additional sum() violations fixed in API, Telegram, and AI tools

Session 5 CAST-AS-TEXT audit. Schema correction: transactions.amount and refunds.amount are numeric(36,18), not integer — all monetary columns in this codebase use numeric(36,18). Fixed 7 CRITICAL violations where DB-level float sums fed bcmath: (1) MerchantSettingsController — fund_flows SUM for API balance response feeds bcadd currency conversion chain. (2) SettlementCurrencyService — fund_flows SUM for getMerchantBalanceByCurrency feeds getMerchantTotalBalance bcadd. (3) SendTelegramDailyReport — refunds.amount sum feeds bccomp/bcadd in report; transaction CASE-SUM for revenue feeds bcadd. (4) ComparePeriodsData AI tool — two transactions.amount sums feed full bccomp/bcdiv/bcmul chain for period-over-period comparison. (5) GetRevenueData AI tool — transactions SUM feeds bcadd/bcdiv per-currency metrics. (6) GetPartnerData AI tool — FIXED wrong column (amount → commission_amount) AND added CAST AS TEXT; wrong column caused silent PostgreSQL errors. All 26 SettlementCurrencyServiceTest pass.

changelog.types.fix
changelog.categories.platform

Financial precision: 11 additional sum() violations fixed across reports, verifier, and refund guards

Extended CAST-AS-TEXT audit to all 58 numeric(36,18) columns across 15 tables. Fixed 11 HIGH violations where DB-level float sums fed bcmath chains: (1) PartnerCommissionService — commission_amount sum guards monthly cap via bcsub (cap bypass risk). (2) PlatformPnLService — total_internal_fee, total_provider_fee, and fund_flows.amount sums (with explicit (float) cast) feed entire P&L calculation chain. (3) PspPerformanceService — paid_amount and total_internal_fee sums feed bcadd-based metrics. (4) FinancialIntegrityVerifier:1295,1323,1546 — verifier used float sums as reference values for bcsub-based discrepancy detection (defeating its own precision checks). (5) RefundService, ProcessClaimRefund, StoreRefundRequest — pending refunds sum guards over-refund prevention via bcsub. Also fixed 4 TreasuryController fund_flows display sums. All tests pass.

changelog.types.fix
changelog.categories.platform

Financial precision: 5 DB-level sum() violations fixed across financial services

Continued CAST-AS-TEXT audit on numeric(36,18) financial columns. Fixed 5 DB-level ->sum('amount') calls that returned PHP float, losing precision on 35B+ totals: (1) FinancialIntegrityVerifier.php:757 — platform_float balance check feeds bcadd. (2) ReconciliationService.php:154 — balance validation with bccomp(..., 18) — 18 decimal places of comparison requires full precision. (3) GetPlatformFinancialsData.php:67 — fund_flows sum for AI P&L reporting. (4) MarkupFeeService.php:182 — markup_fees.amount is also numeric(36,18) (same DB schema as fund_flows). Return type changed float→string. (5) FundFlowReportService.php:120 — opening balance sum feeds bcadd(opening, netChange, 18). All 123 related tests pass (359 assertions).

changelog.types.fix
changelog.categories.platform

Financial integrity verifier — 3 additional false-positive checks fixed, 30 tests

Continued audit of FinancialIntegrityVerifier staging false positives: (1) amount_consistency (additional fix): REFUND query lacked psp_reference filter — seeder-created refunds had r.amount stored in dollars instead of cents. Added whereExists subquery through r.transaction_id → payment_attempts → psp_reference. (2) fund_flow_vs_transaction_fee (HIGH, 200): seeder creates TransactionFees without fund flows — added whereNotNull('pa.psp_reference'). (3) transaction_fee_completeness (HIGH, 200): seeder creates SUCCESS payment_attempts without TransactionFee records — added whereNotNull('pa.psp_reference'). Total false positives eliminated: 2400+ HIGH/CRITICAL → 0. Added 2 new tests (total: 30 tests, 81 assertions). Remaining 25 issues in staging are known seeder data quality artifacts.

changelog.types.fix
changelog.categories.platform

Financial integrity verifier — 4 staging false-positive checks fixed

FinancialIntegrityVerifier was generating ~1500+ false-positive alerts on staging: (1) amount_consistency (CRITICAL, 200): verifier compared fund_flows.amount [dollars] with refunds.amount [cents] directly — 100x unit mismatch. Fixed by dividing r.amount by 100.0 in SQL and bcdiv(..., '100', 18) in PHP. (2) fund_flow_completeness (HIGH, 973): 297,848 seeder-created payment_attempts have no fund flows (StagingSeeder bypasses service layer). Added whereNotNull('pa.psp_reference') filter — real PSP-processed attempts always have psp_reference. Zero real transactions are missing fund flows. (3) settlement_timing_accuracy (HIGH, 500): seeder sets transaction_fees.settlement_at to fixed date, fund_flows.released_at to arbitrary value — gaps up to 71 days. Same psp_reference filter applied. (4) reserve_release_timing (HIGH, 500): same seeder artifact root cause, same fix.

changelog.types.fix
changelog.categories.platform

Financial integrity verifier false positives eliminated — REFUND, PROCESSING_FEE, MDR

FinancialIntegrityVerifier was triggering hundreds of false HIGH alerts per day. Root causes: (1) morphMap added 'refund'/'dispute' aliases without data migration — verifier queries using FQCN missed 30K rows with aliases. Fixed with whereIn(both formats) + chunked migration (FOR UPDATE SKIP LOCKED). (2) Zero-fee merchants have no PROCESSING_FEE/MDR fund flows (DB constraint prevents zero-amount entries). Fixed by joining transaction_fees and conditionally requiring fee flows only when total_fees > 0. (3) TransactionFactory::withPartialRefund/withFullRefund bypassed service layer — fixed to call FundFlowService::createRefundCompleteEntries after factory creation.

changelog.types.fix
changelog.categories.platform

Platform float precision loss fixed — CAST(SUM AS TEXT) replaces ->sum('amount')

FundFlowService::getPendingSettlement, getReserveBalance, getSystemBalance, and getPspFreezeReserveBalance all used Eloquent ->sum('amount') which PHP cast to float, losing precision on amounts > 10 billion (IEEE 754 double only has 15-17 significant digits). FinancialIntegrityVerifier::verifyPlatformFloat also affected. Fixed all 6 locations with selectRaw('CAST(COALESCE(SUM(amount), 0) AS TEXT) as precise_sum')->first()?->precise_sum. Also replaced abs() with bcmul($n, '-1', 18) for consistency.

changelog.types.fix
changelog.categories.payment

S2S paid_amount zero — fund flows and refund validation now correct

S2S (server-to-server) payments were not setting paid_amount on payment_attempts or transactions after PSP response. ProcessSandboxWebhook read paid_amount=0, creating zero-amount fund flows. RefundService compared dollars against cents (10000 vs 80.00), allowing over-refunds silently. Fixed by storing paid_amount in dollars (cents÷100) at S2S response time.

changelog.types.fix
changelog.categories.payment

Double KYB notification — removed duplicate call from controller

KYB approve/reject controller explicitly called notifyKybApproved/Rejected AND the KybObserver also fired on model update, sending two notifications. Removed the explicit controller calls; observer is now the single source of notification side effects.

changelog.types.fix
changelog.categories.payment

Float arithmetic in dispute chargeback calculation replaced with bcmath

DisputeResolutionService used round($paid - $refunded, 2) for chargeback amount — vulnerable to IEEE 754 precision loss on large amounts. Replaced with bcsub() and bccomp() for PCI-safe precision.

changelog.types.fix
changelog.categories.platform

Sandbox transactions stuck PENDING — now correctly expired

ProcessTransactionExpire skipped sandbox transactions because PspStatusQueryService returned ERROR for SandboxGateway (unsupported). Added SandboxGateway branch returning notFound() result, which correctly allows expiration. Resolves 4,661 historical failed jobs.

changelog.types.security
changelog.categories.payment

PCI: Remove card data from browser console logs and fix postMessage targetOrigin

Removed console.log('Payment submitted:', formData) from Sandbox/PaymentForm.vue that exposed full PAN + CVV in browser.log. Fixed postMessage wildcard targetOrigin ('*') to window.location.origin in both PaymentForm.vue and AirWallex/DropInElement.vue to prevent card data interception by malicious parent frames.

changelog.types.breaking
changelog.categories.api

browserDetails fields renamed to camelCase and all required

S2S API browserDetails nested field names changed from snake_case to camelCase (e.g., user_agent → userAgent, screen_width → screenWidth). All 12 fields are now required. New Browser Details JS SDK available at /js/epayse-browser.js for easy collection.

changelog.types.feature
changelog.categories.payment

Refund Approval Workflow & Admin Queue

Added admin refund approval queue with approve/reject/retry actions, auto-refund toggle per merchant (for PSPs without refund API), REFUND_HOLD fund flow for balance reservation, Telegram notifications for refund/chargeback events, balance-insufficient handling with admin intervention, and per-merchant refund configuration in admin settings.

changelog.types.feature
changelog.categories.payment

Merchant self-serve PSP onboarding requests

Merchants can now submit access requests for additional payment service providers from their dashboard. Admins review, approve (auto-assigning the PSP), or reject requests with notes. Includes full status workflow: pending → under_review → approved/rejected.

changelog.types.fix
changelog.categories.settlement

Fix 7 financial accuracy bugs in settlement, fees, and fund flow

Centralized fee calculation into PaymentFeeCalculator (was duplicated in 4 places with float bugs), fixed GlobalFeeConfig to use bcmath, added disputes.amount column enabling DISPUTE_HOLD/WIN_RELEASE, fixed balance inflation (total doubled after T+N), fixed DailyCalculation to use disputes.amount, added lazy PENDING→COMPLETED transition for settlement release, optimized getSystemBalance from O(N) to O(1) with DISTINCT ON + Redis cache.

changelog.types.feature
changelog.categories.platform

Add financial operations monitoring and management tools

Settlement-ledger auto-reconciliation (nightly), chargeback ratio monitoring with Visa VAMP/Mastercard ECM threshold alerts, manual payout creation for admin, treasury dashboard with platform-wide balance overview, admin audit log viewer, and cross-merchant P&L report with monthly breakdown.

changelog.types.feature
changelog.categories.platform

Ops Hub - Centralized DevOps Dashboard

Added Ops Hub page in admin portal (System > Ops Hub) with categorized links to all monitoring and developer tools (Horizon, Log Viewer, Dozzle, Uptime Kuma, Beszel, GlitchTip, Dokploy, Telescope, Mailpit). Features environment-aware URL resolution, live system health panel, and access type badges.

changelog.types.feature
changelog.categories.platform

Provider-agnostic AI Chat with pre-fetch data pattern

Refactored AI Chat features (KYB AI Chat, Platform AI Chat) to use provider-agnostic pre-fetch pattern. Claude CLI is primary (flat-rate subscription), with Gemini/OpenAI/Anthropic API as fallback. ContextProvider interface pre-fetches data from DB, injects into prompt, eliminating tool-calling dependency. Includes AbortController for SSE cleanup, typewriter effect for UX, unique delimiters for prompt security, partial tool failure resilience, and claude-cli-prefetch-pattern skill.

changelog.types.fix
changelog.categories.platform

Fix AI Ops hardcoded /operate URL prefix and feedback route issues

Replaced hardcoded /operate/ URLs in 4 AiOps Vue components with Ziggy route() helper for subdomain routing compatibility. Fixed KYB Intelligence feedback controller to return RedirectResponse instead of JsonResponse for Inertia compatibility. Fixed ScopeValidator rejecting plural keywords (merchants, transactions) by adding admin-specific keyword list.

changelog.types.fix
changelog.categories.security

Fix AI Chat Widget server hang on close/re-open

Added AbortController to useStreamingChat composable. Closing chat now aborts in-flight SSE fetch requests, preventing PHP worker exhaustion (504). Added 90s timeout on fallback axios calls.

changelog.types.improvement
changelog.categories.platform

Restructure seeders into 3-tier architecture with fund flow production parity

DatabaseSeeder now lean (~37s, foundation + accounts only). StagingSeeder provides full demo data with 7 new feature seeders. MassTransactionSeeder generates 7 fund flow types per transaction matching production FundFlowService, creates merchant_reserves records, uses bcmath scale 18. 15 unit tests with 252 assertions verify balance integrity.

changelog.types.feature
changelog.categories.platform

AI Operations Platform — Fraud Intelligence + Unified Dashboard

Phase 3: Fraud Intelligence with FraudIntelligenceReport model, FraudIntelligenceService (risk analysis, rule suggestions), GetFraudIntelligenceData AI tool integrated into AdminAnalyticsAgent. Phase 4: Unified AI Ops Dashboard with cross-domain stats (KYB, Fraud, Website, Learnings), activity feed, Learnings management page, and Fraud Intelligence index/detail pages. Admin sidebar AI Operations section with 4 sub-items. 15 PHPUnit + 53 Vitest + 8 E2E tests.

changelog.types.feature
changelog.categories.platform

Add Telegram AI merchant support bot

AI-powered Telegram bot for per-merchant group support. Hybrid LLM (Gemini for data queries + Claude CLI for integration docs), 15 AI tools, 2-phase comfort response, /sandbox command with email credentials delivery, webhook security with dual verification, per-group rate limiting, and docs extraction pipeline.

changelog.types.feature
changelog.categories.platform

CI/CD pipeline Telegram notifications

Real-time Telegram alerts for every CI/CD pipeline stage (deploy start/success/fail, smoke test results) with multi-channel architecture supporting separate channels for CI/CD, app alerts, and monitoring

changelog.types.feature
changelog.categories.platform

Comprehensive system error Telegram alerting

Fix broken telegram logging channel, add global exception and queue failure handlers, PSP gateway error alerts, fraud BLOCK/REVIEW notifications, DDoS detection alerts, security monitoring Telegram channel, fund flow discrepancy alerts, and scheduled task failure monitoring — coverage from 7 scenarios to near 100% critical paths

changelog.types.fix
changelog.categories.platform

Fix route cache duplicate names breaking staging deploy

Renamed sandbox.* route names to gateway.sandbox.*, main.sandbox.*, merchant.sandbox.* to prevent artisan route:cache failure on staging/production

changelog.types.fix
changelog.categories.dashboard

Fix merchant statements page crash on empty currency config

Fixed dead code toArray() ?? fallback and added null safety guard for merchants without currency configurations

changelog.types.fix
changelog.categories.platform

Fix CollectStatusMetrics missing Request parameter

Status metrics collection command failed after health monitoring auth was added, now creates internal authenticated request

changelog.types.improvement
changelog.categories.platform

Add 43 production readiness regression tests

19 PHPUnit tests verifying security config, bcmath enforcement, HTTP timeouts, onOneServer scheduling + 24 Playwright E2E staging tests for Sections 37-41

changelog.types.feature
marketing

AI Agent Phase 4 — Social Autopilot, sub-agent orchestration & weekly learning aggregation

SocialAutopilotAgent with 4 capabilities (analyze, generate social content, approve content, generate report). ApproveContentHandler delegates to SocialContentService::approve(). SubAgentCallHandler enables parent-child agent session spawning via AgentOrchestratorService. GenerateAgentLearningsJob runs weekly to aggregate cross-session learnings (send times, content patterns, channel effectiveness). All 6 agents + 17 handlers fully registered. 8 new tests.

changelog.types.feature
marketing

AI Agent Phase 3 — Churn Prevention, Performance Optimizer & auto-trigger

ChurnPreventionAgent (7 capabilities) and PerformanceOptimizerAgent (5 capabilities). 4 new handlers: DetectChurnHandler, AdjustBudgetHandler, PauseCampaignHandler, SendCampaignHandler — all delegating to existing services. AutoTriggerChurnPreventionJob runs daily at 09:00 to detect at-risk merchants and auto-start retention campaigns. 20 new tests.

changelog.types.feature
marketing

AI Agent Phase 2 — Content Creator & Lead Nurturing agents

ContentCreatorAgent (6 capabilities) and LeadNurturingAgent (6 capabilities). 4 new action handlers: GenerateSocialContentHandler, CreateDripCampaignHandler, EnrollMerchantsHandler, ScoreLeadsHandler — all delegating to existing SocialContentService, DripCampaignService, and LeadScoringService. 18 new tests.

changelog.types.fix
marketing

AI Agent Phase 1.5 — critical infrastructure bug fixes

Fix WebSocket event name mismatch (broadcastAs vs composable listen). Wire up MarketingAgentServiceProvider to register agents at boot. Dispatch AgentAutoApprovalTimeoutJob for review-level actions. Close learning feedback loop via recordSessionLearnings() on session completion. Remove dead Create.vue page. 14 new tests.

changelog.types.feature
marketing

AI Agent Marketing Automation — Core Framework & Campaign Orchestrator

Autonomous campaign orchestration with 3-tier approval matrix (Auto/Review/Critical), real-time WebSocket timeline via Laravel Reverb, 7 action handlers (analyze, generate, create template/campaign/experiment, schedule, report), human-in-the-loop approval flow, and CampaignOrchestratorAgent as first concrete agent. 3 database tables, 6 enums, 6 core services, 3 Vue pages with live action timeline, useAgentSession composable. 89 BE + 65 FE tests.

changelog.types.feature
marketing

Marketing Intelligence Phase 8 — batch operations, notifications, snooze & performance

Batch accept/dismiss for multiple recommendations with floating toolbar and confirmation dialog. Snooze action extends expires_at by 7 days. NewRecommendationsNotification dispatched after daily generation. Weekly marketing digest email job scheduled Monday 9AM. Stats caching (5 min TTL) with cache-busting on mutations. Composite database indexes on marketing_recommendations (status+created_at, status+expires_at). Select-all checkbox with ring highlight on selected cards. 20 BE + 7 FE new tests.

changelog.types.feature
marketing

Marketing Intelligence Phase 7 — Claude CLI integration & AI content generator

Add ClaudeCodeService wrapping Claude Code CLI for flat-rate AI generation (Claude Max). Refactor AiInsightService with dual provider support (claude_cli vs anthropic_api). New AiContentGeneratorService with 4 methods: email subjects, email body, landing copy, ad copy — all bilingual EN/VI with JSON parsing and code block extraction. Content Generator page with dynamic forms, copy-to-clipboard, and loading states. 70 BE + 15 FE Vitest tests.

changelog.types.feature
marketing

Marketing Intelligence Phase 6 — action automation, audit trail & detail page

Implement 3 missing apply methods (CreateDripCampaign, AdjustScoring, SendReEngagement) in CampaignOptimizerService. Add RecommendationAuditLog model with ULID prefix ral_ for tracking accept/dismiss/apply/rate actions. New Intelligence Show page with badges, star rating, analysis/outcome data, activity timeline, related recommendations and dismiss dialog. 35 Vitest + 16 PHPUnit tests.

changelog.types.improvement
marketing

Sync Intelligence page UI/UX with Admin Portal conventions

Responsive header with mobile breakpoints matching 110+ admin pages. Replace manual pagination loop with standard Pagination component (supports ellipsis, Go to page, filter preservation). Mobile-optimized recommendation cards and touch targets. Fix Playwright MCP Chrome session conflict with --isolated flag and session-start cleanup hook.

changelog.types.feature
changelog.categories.platform

Buyer Shield — claim fees & claim_reference removal

Remove human-readable claim_reference (RSLV-XXXXXX) in favor of standard ULIDs. Add configurable claim fees per merchant per payment method (mirroring dispute fees). Fee charged on claim resolution (5 paths). CLAIM_FEE fund flow entries. Settlement NET formula updated to 7 components. Admin UI fee config tab. 34 new tests (18 BE + 16 FE).

changelog.types.feature
changelog.categories.platform

Buyer Shield — plan gap completion (evidence upload, bulk ops UI, email i18n)

Evidence upload routes for buyer and merchant with file validation and IDOR protection. Admin bulk operations UI with checkbox row selection, floating action bar, assign/arbitrate dialogs. Bilingual Vietnamese sections in buyer-facing email templates. E2E buyer claim lifecycle tests (12 tests). Evidence upload feature tests (10 tests).

changelog.types.feature
changelog.categories.platform

Buyer Shield Phase 4E — bulk operations & claim rate analytics

Admin bulk assign (escalated claims to reviewer) and bulk arbitrate (approve/deny multiple claims). Merchant claim rate analytics with risk level badge (Low/Medium/High) and 30-day trend detection on admin merchant detail page. 11 new tests.

changelog.types.feature
changelog.categories.platform

Buyer Shield Phase 4D — i18n & merchant response templates

Full i18n for 6 Resolve Vue pages and ResolveLayout (~180 translation keys, EN + VI). Config-based merchant response templates (6 presets: full refund, partial, reject) with quick-apply UI in claim response form. 3 new response template tests.

changelog.types.feature
changelog.categories.platform

Buyer Shield Phase 4 — auto-resolution & webhook events

Auto-resolution engine for claims below merchant's auto_refund_threshold (synchronous check at filing time, async PSP refund via ProcessClaimRefund). Webhook event system dispatching claim.filed, claim.auto_resolved, claim.escalated, and claim.resolved events via ProcessSendClaimWebhookToMerchant job with exponential backoff. WebhookPayloadBuilder extended with buildClaimObject() for Stripe-like event payloads. 19 new tests (8 auto-resolution + 11 webhook) across 2 test files.

changelog.types.security
changelog.categories.payment

Webhook Safety Guard — cross-transaction prevention

Defense-in-depth system preventing cross-transaction status updates from webhooks. PaymentIdentity VO, WebhookSafetyGuard runtime validator, auto-discovery GatewayRegistryTest (fails on missing contract tests), ReconcileTransactionStatusesCommand (daily reconciliation), legacy gateway fixes (JPay/PinddPay). 23 contract tests across 7 gateways, 527 gateway tests pass.

changelog.types.feature
changelog.categories.security

EPaySe Buyer Protection (Buyer Shield) Phase 1 MVP

Buyer-facing dispute resolution portal with OTP/magic link verification, claim filing, and status tracking. Includes admin claim management with export, merchant dashboard claims view, SHA-256 token hashing, HKDF-derived session keys, TOCTOU protection, rate limiting, and IDOR prevention. 88 tests across 6 test files (161 total with related tests).

changelog.types.feature
changelog.categories.platform

Marketing analytics dashboard & lead scoring (Phase 5)

Analytics dashboard with marketing funnel visualization, lead scoring engine (100-point algorithm across 7 factors), churn detection via volume decline analysis, campaign performance tracking, and score distribution. Includes daily scheduled jobs for funnel snapshots and lead score recalculation, Inertia deferred props for fast page load, and comprehensive test coverage (28 backend + 6 frontend tests).

changelog.types.feature
changelog.categories.platform

Landing pages, case studies & lead capture (Phase 4)

Extends Article model with type field (ARTICLE, LANDING_PAGE, CASE_STUDY) for SEO-optimized landing pages and case studies. Includes public landing page rendering with embedded lead capture forms, lightweight lead capture endpoint with UTM tracking, admin lead management (list, detail, status workflow, assignment), newsletter auto-subscribe on consent, and blog admin type selector. 62 tests (42 backend + 20 frontend).

changelog.types.feature
changelog.categories.platform

PSP match notification & smart re-engagement (Phase 3)

Smart re-engagement system that matches merchants with new PSPs based on their payment interests. Includes merchant payment interest widget (dashboard), PSP match scoring engine (method groups, industry, volume), admin preview and bulk notification, email notifications via PspMatchMail, anti-spam protection, and KYB auto-enrichment. 54 tests (32 backend + 22 frontend).

changelog.types.feature
changelog.categories.platform

Lifecycle drip campaigns & email templates (Phase 2)

Automated lifecycle drip campaigns with multi-step email sequences triggered by merchant status changes. Includes drip campaign CRUD with step editor, email template management with variable substitution, merchant enrollment lifecycle (active/paused/cancelled/completed), conditional step execution (skip_if_status/require_status), campaign metrics and completion rates, scheduled daily processing, and auto-enrollment via MerchantStatusChanged event. 85 tests (50 backend + 35 frontend).

changelog.types.feature
changelog.categories.platform

Email marketing & newsletter system (Phase 1)

Complete email marketing platform with newsletter subscribers, email campaigns, email templates, and campaign recipient tracking. Includes admin CRUD for campaigns/subscribers, public newsletter subscription with double opt-in, unsubscribe flow, preference center, blog widget integration, and comprehensive test coverage (98 backend + 22 frontend + 55 E2E tests).

changelog.types.improvement
changelog.categories.platform

AI Assistant quick questions marquee animation

Replaced JS step-by-step carousel (setInterval + drag/swipe) with CSS marquee animation for Quick Questions in AI chat widget. Smooth continuous scroll, hover-to-pause, edge fade effect, prefers-reduced-motion support.

changelog.types.improvement
changelog.categories.security

Merchants must configure own AI API key

Removed system default LLM fallback for merchant portal. Merchants now must configure their own AI API key (OpenAI, Gemini, or Claude) to use the AI Assistant. Widget shows clear setup guide with CTA when unconfigured. Admin portal unchanged (uses system provider).

changelog.types.feature
changelog.categories.fraud

AI-powered fraud analysis tools for chat assistants

Added 6 fraud analysis tools to AI agents: 3 merchant tools (fraud summary with decision breakdown/risk distribution/daily trends, blacklist overview, rule effectiveness with trigger analysis) and 3 admin tools (platform-wide fraud overview, global fraud signal intelligence, learned rule performance). Merchant agent now has 10 tools, admin agent has 7. Includes ScopeValidator fraud keyword support. 28 new tests.

changelog.types.feature
changelog.categories.platform

SSE streaming responses for AI assistants

Added Server-Sent Events (SSE) streaming to both merchant and admin AI chat endpoints. Responses now appear progressively in real-time instead of waiting 3-10 seconds for blocking completion. Cache hits return JSON instantly. Includes new useStreamingChat composable with debounced markdown rendering, fallback to blocking on stream failure, and blinking cursor UX during streaming. 15 backend + 20 frontend tests.

changelog.types.feature
changelog.categories.platform

Agent + Tool Calling architecture for AI assistants

Replaced Two-Pass LLM architecture (parameter extraction → context building → answer generation) with Laravel AI SDK Agent + Tool Calling. Merchant agent has 8 tools (revenue, transactions, refunds, disputes, settlements, fraud, balance, compare), admin agent has 4 tools (platform overview, merchant ranking, PSP performance, financials). Includes 3 middleware (input sanitization, prompt leakage protection, token tracking). All 12 providers use agent path. 34 new tests with 103 assertions.

changelog.types.improvement
changelog.categories.platform

Replace LlmProviderFactory with Laravel AI SDK-based service

Replaced manual HTTP provider calls (752 LOC) with LlmSdkProviderService using Laravel AI SDK. All 12 LLM providers route through the SDK's unified API, including Together AI and Perplexity via OpenAI-compatible driver. CircuitBreaker integration preserved.

changelog.types.improvement
changelog.categories.platform

Remove Two-Pass legacy code and unsupported providers

Removed HuggingFace and AWS Bedrock providers (no SDK support), Two-Pass LLM architecture (parameter extraction + context building + answer generation), and all related code: LlmProviderFactory, MerchantDataContextBuilder, AdminDataContextBuilder, ParameterValidator, prompt template system. Reduced from 14 to 12 providers, all using Agent + Tool Calling. ~1,500 LOC removed.

changelog.types.feature
changelog.categories.api

Add conversion rate and total transactions to Payment Methods API

Payment Methods API now returns conversion_rate (success percentage) and total_transactions for each payment method, calculated from the merchant's last 30 days of payment attempt data. Cached for 5 minutes per merchant to minimize DB load.

changelog.types.feature
changelog.categories.api

Add preferredPaymentMethod parameter to transaction creation APIs

All 3 transaction creation endpoints (standard, iframe, S2S) now accept an optional preferredPaymentMethod parameter. When specified, the preferred method is tried first in the cascade order. For S2S, silently falls back to default gateway if the preferred method doesn't support S2S. Includes audit trail via payload_checkout.

changelog.types.feature
changelog.categories.fraud

Transaction alert indicators with detail dialog and severity filter

Added alert indicator column (first column) to admin transactions DataTable showing fraud and website mismatch warnings per transaction. Clicking an indicator opens a dialog with risk score bar, decision badges, and alerts grouped by category (geographic, website, fraud rules, risk score, blacklist, velocity, card testing, AML, browser, manual review). Added severity-based filter (Has Alerts, Critical, High, Medium, No Alerts) with URL persistence. Includes 31 backend tests, 23 frontend tests, and 10 E2E tests.

changelog.types.improvement
changelog.categories.fraud

Deferred fraud scoring and Redis-based frequency counting

Two-phase fraud evaluation defers 8 scoring-only checks for clean transactions (~95%) via Laravel defer(), reducing latency by 40-80ms. Redis atomic counters replace DB COUNT queries for calendar-window frequency rules, cutting per-rule check time from 10-30ms to 1-5ms. Falls back to DB automatically when Redis is unavailable.

changelog.types.improvement
changelog.categories.fraud

Cache fraud detection and payment routing for faster transactions

Added configurable caching layer across 6 services (FrequencyChecker, BlacklistChecker, FraudDetectionService, TransactionExpirationService, CascadingPaymentService, PaymentRoutingService) reducing DB queries from 31-53 to ~10-20 per transaction. Added FRAUD_CACHE_STORE config, early exit on blacklist block, batch country validation, and DB indexes for fraud queries.

changelog.types.fix
changelog.categories.platform

Fix alert service bugs and add comprehensive unit tests

Fixed 3 production bugs in alert notification pipeline: undefined array key access in Telegram/Slack services, void return type preventing sendTestAlert from returning results, and deduplication never recording fingerprints. Added 44 unit tests for TelegramAlertService, SlackAlertService, and AlertDispatcher.

changelog.types.improvement
changelog.categories.payment

Country-dependent billing state combobox and postal code hints

Billing state field now shows a searchable combobox with states/provinces for supported countries. Users can select from the list or type custom values. Postal code placeholder dynamically shows format hints based on selected country.

changelog.types.feature
changelog.categories.fraud

Amount-based velocity limits

Frequency rules can now limit by total transaction amount (e.g., block if IP exceeds $10,000/day) in addition to transaction count

changelog.types.feature
changelog.categories.fraud

Calendar-based time windows

Added calendar hour/day/week/month windows that reset at natural boundaries (midnight, start of week/month) instead of rolling periods

changelog.types.fix
changelog.categories.platform

Fix false-positive financial integrity alerts for missing REFUND fund flows

FinancialIntegrityVerifier was generating false HIGH alerts for 15,000+ completed refunds due to morph map mismatch: the Relation::morphMap short alias ('refund') was added without a data migration, so getMorphClass() returned 'refund' while existing fund_flows rows stored 'App\\Models\\Refund'. Fixed verifier to accept both formats via whereIn. Added chunked migration (SKIP LOCKED) to normalize non-Transaction rows. Fixed TransactionFactory::withPartialRefund()/withFullRefund() to create fund flows via FundFlowService. Added backfill:refund-fund-flows command for future gaps.

Payment Links, browser anti-spoofing, and developer experience improvements

changelog.types.feature
changelog.categories.payment

Payment Links

Merchants can create shareable payment links for no-code payment collection with 6 customer fields and customer selector

changelog.types.security
changelog.categories.payment

Signed URL verification for Payment Links

Payment links now use signed URLs to prevent tampering and unauthorized access

changelog.types.feature
changelog.categories.security

Browser details collection & anti-spoofing detection

Collect browser fingerprint details and detect spoofed user agents for fraud prevention

changelog.types.improvement
changelog.categories.platform

Vue MCP debugging integration

Integrated vite-plugin-vue-mcp for AI-assisted Vue component debugging

changelog.types.improvement
changelog.categories.platform

Setup command improvements

Improved /setup command with APP_SERVICE check, seeder fallback, and hosts validation

Exchange rate markups, rolling reserves, and infrastructure modernization

changelog.types.feature
changelog.categories.payment

Exchange rate markup management

Configure exchange rate markups for currency conversion with merchant-specific rates

changelog.types.feature
changelog.categories.settlement

Rolling reserve management

Complete rolling reserve system with merchant filters, export, and integration tests

changelog.types.feature
changelog.categories.dashboard

Landing page redesign

Redesigned landing page with enhanced visuals, animations, and live checkout component

changelog.types.feature
changelog.categories.platform

A/B testing system

Checkout optimization through A/B testing with variant support and i18n

changelog.types.feature
changelog.categories.platform

Status page system

Public status page with admin management, live indicators, and API documentation

changelog.types.improvement
changelog.categories.platform

Docker service modernization

Renamed Docker service from laravel.test to epayse.app with auto-detect LAN IP

changelog.types.improvement
changelog.categories.dashboard

Mobile scroll-snap blog slider

Horizontal scroll-snap slider for blog cards on mobile with sticky stack cards

changelog.types.security
changelog.categories.security

Session security tightening

Reduced session lifetime to 30min and remember cookie to 8 hours

changelog.types.fix
changelog.categories.dashboard

FX Markups mobile tab overlap

Resolved mobile tab overlap on FX Markups page and DataTable pagination

Issue reports, admin permissions overhaul, and partner program foundation

changelog.types.feature
changelog.categories.dashboard

Issue reports

Merchants can report errors directly from the dashboard with admin notification

changelog.types.feature
changelog.categories.partner

Partner program foundation

Multi-tier referral system with commission tracking, tier-based rates, and downline management

changelog.types.security
changelog.categories.security

Admin authorization overhaul

Added authorization checks to all admin controllers following OWASP A01 guidelines

changelog.types.improvement
changelog.categories.platform

Redis operation service

Standardized cache layer with RedisOperationService for consistent cache operations

changelog.types.fix
changelog.categories.security

Admin permission mismatches

Resolved 4 admin permission mismatches causing 403 errors

PSP management, fund flows, and manual payment reconciliation

Documentation portal, customer management, and API improvements

S2S payment API, security hardening, and checkout improvements

Sandbox system, dispute management, and blog platform

Initial release of EPaySe Payment Gateway Platform