Skip to main content

Phân tích hệ thống

# TÀI LIỆU PHÂN TÍCH HỆ THỐNG (SA) - SMARTPOST PAYMENT SERVICE V4 FINAL ## MỤC LỤC 1. [Tổng quan kiến trúc](#1-tổng-quan-kiến-trúc) 2. [Cấu trúc thư mục Laravel + DDD](#2-cấu-trúc-thư-mục-laravel--ddd) 3. [Danh sách API Endpoints](#3-danh-sách-api-endpoints) 4. [Chi tiết API Request/Response](#4-chi-tiết-api-requestresponse) 5. [Middleware và Authentication](#5-middleware-và-authentication) 6. [Xử lý Webhook](#6-xử-lý-webhook) 7. [Xử lý các trường hợp đặc biệt](#7-xử-lý-các-trường-hợp-đặc-biệt) 8. [Audit Log và Monitoring](#8-audit-log-và-monitoring) 9. [Jobs và Queue Processing](#9-jobs-và-queue-processing) 10. [Error Handling](#10-error-handling) 11. [Performance và Scaling](#11-performance-và-scaling) 12. [Security và Compliance](#12-security-và-compliance) --- ## 1. TỔNG QUAN KIẾN TRÚC ### 1.1. Kiến trúc hệ thống ``` ┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐ │ SmartPost │────▶│ Payment Service │────▶│ Payment Gates │ │ Projects │◀────│ (Multi-tenant) │◀────│ VNPay|MoMo|Zalo │ │ [SM,TMS,TICKET] │ └─────────────────┘ └─────────────────┘ └─────────────────┘ │ │ │ ▼ ▼ ▼ ┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐ │ Database │ │ External │ │ Load Balancer │ │ (MySQL 8.0) │ │ Services │ │ (Nginx/HAProxy)│ └─────────────────┘ │ [Kafka/Redis] │ └─────────────────┘ │ └─────────────────┘ ▼ ┌─────────────────┐ │ Monitoring │ │ [ELK + Grafana] │ └─────────────────┘ ``` ### 1.2. Tech Stack - **Framework**: Laravel 11.x - **Architecture**: Domain Driven Design (DDD) + CQRS - **Database**: MySQL 8.0 (Master-Slave for read scaling) - **Cache**: Redis Cluster - **Queue**: Redis + Laravel Horizon - **Search**: Elasticsearch 8.x - **Monitoring**: ELK Stack + Grafana + Prometheus - **Container**: Docker + Kubernetes - **Authentication**: Internal service (Network security) - **API Documentation**: OpenAPI 3.0 + Swagger UI - **Testing**: PHPUnit + Pest + Feature Tests ### 1.3. Security Model - **Network Security**: Service được bảo vệ bởi VPN, Firewall và Service Mesh - **Request Validation**: Validate project_code và app_code từ internal registry - **Webhook Security**: Multi-layer signature validation - **Rate Limiting**: Intelligent rate limiting với Redis - **Data Encryption**: AES-256 cho sensitive data - **Audit Trail**: Comprehensive audit logging - **GDPR Compliance**: Data retention và deletion policies ### 1.4. Deployment Architecture ``` ┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐ │ Production │ │ Staging │ │ Development │ │ │ │ │ │ │ │ - 3 App Servers │ │ - 1 App Server │ │ - Local Docker │ │ - 2 DB Servers │ │ - 1 DB Server │ │ - SQLite/MySQL │ │ - 3 Redis Nodes │ │ - 1 Redis Node │ │ - Redis Local │ │ - Load Balancer │ │ - Simple LB │ │ - No LB │ │ - Auto Scaling │ │ - Manual Scale │ │ - Manual │ └─────────────────┘ └─────────────────┘ └─────────────────┘ ``` --- ## 2. CẤU TRÚC THƯ MỤC LARAVEL + DDD ``` src/ ├── app/ │ ├── Console/ │ │ ├── Commands/ │ │ │ ├── System/ │ │ │ │ ├── CheckSystemHealthCommand.php │ │ │ │ ├── CleanupOldLogsCommand.php │ │ │ │ ├── GenerateSystemReportCommand.php │ │ │ │ └── UpdateSystemConfigCommand.php │ │ │ ├── Transaction/ │ │ │ │ ├── ProcessFailedTransactionsCommand.php │ │ │ │ ├── ReconcileTransactionsCommand.php │ │ │ │ ├── RetryFailedCallbacksCommand.php │ │ │ │ └── CleanupExpiredTransactionsCommand.php │ │ │ ├── Webhook/ │ │ │ │ ├── ProcessFailedWebhooksCommand.php │ │ │ │ ├── RetryWebhookDeliveryCommand.php │ │ │ │ └── ValidateWebhookConfigCommand.php │ │ │ └── Maintenance/ │ │ │ ├── ArchiveOldDataCommand.php │ │ │ ├── OptimizeDatabaseCommand.php │ │ │ └── BackupCriticalDataCommand.php │ │ └── Kernel.php │ │ │ ├── Domain/ │ │ ├── Project/ │ │ │ ├── Entities/ │ │ │ │ ├── Project.php │ │ │ │ └── ProjectConfiguration.php │ │ │ ├── ValueObjects/ │ │ │ │ ├── ProjectCode.php │ │ │ │ ├── ProjectStatus.php │ │ │ │ └── Currency.php │ │ │ ├── Repositories/ │ │ │ │ └── ProjectRepositoryInterface.php │ │ │ ├── Services/ │ │ │ │ ├── ProjectService.php │ │ │ │ ├── ProjectConfigurationService.php │ │ │ │ └── ProjectValidationService.php │ │ │ └── Events/ │ │ │ ├── ProjectCreated.php │ │ │ ├── ProjectActivated.php │ │ │ └── ProjectDeactivated.php │ │ │ │ │ ├── Tenant/ │ │ │ ├── Entities/ │ │ │ │ ├── Tenant.php │ │ │ │ └── TenantPaymentProvider.php │ │ │ ├── ValueObjects/ │ │ │ │ ├── AppCode.php │ │ │ │ ├── TenantStatus.php │ │ │ │ └── BusinessType.php │ │ │ ├── Repositories/ │ │ │ │ ├── TenantRepositoryInterface.php │ │ │ │ └── TenantPaymentProviderRepositoryInterface.php │ │ │ ├── Services/ │ │ │ │ ├── TenantService.php │ │ │ │ ├── TenantOnboardingService.php │ │ │ │ └── TenantConfigurationService.php │ │ │ └── Events/ │ │ │ ├── TenantCreated.php │ │ │ ├── TenantProviderConfigured.php │ │ │ └── TenantLimitUpdated.php │ │ │ │ │ ├── Payment/ │ │ │ ├── Entities/ │ │ │ │ ├── Transaction.php │ │ │ │ ├── PaymentProvider.php │ │ │ │ ├── TransactionEvent.php │ │ │ │ └── Refund.php │ │ │ ├── ValueObjects/ │ │ │ │ ├── Money.php │ │ │ │ ├── TransactionStatus.php │ │ │ │ ├── PaymentMethod.php │ │ │ │ └── TransactionReference.php │ │ │ ├── Repositories/ │ │ │ │ ├── TransactionRepositoryInterface.php │ │ │ │ ├── PaymentProviderRepositoryInterface.php │ │ │ │ └── RefundRepositoryInterface.php │ │ │ ├── Services/ │ │ │ │ ├── TransactionService.php │ │ │ │ ├── PaymentProviderService.php │ │ │ │ ├── RefundService.php │ │ │ │ ├── FraudDetectionService.php │ │ │ │ └── PaymentProviderFactory.php │ │ │ ├── Adapters/ │ │ │ │ ├── VNPayAdapter.php │ │ │ │ ├── MoMoAdapter.php │ │ │ │ ├── ZaloPayAdapter.php │ │ │ │ └── NapasAdapter.php │ │ │ └── Events/ │ │ │ ├── TransactionCreated.php │ │ │ ├── TransactionCompleted.php │ │ │ ├── TransactionFailed.php │ │ │ ├── RefundInitiated.php │ │ │ └── FraudDetected.php │ │ │ │ │ ├── Webhook/ │ │ │ ├── Entities/ │ │ │ │ ├── WebhookLog.php │ │ │ │ ├── WebhookDelivery.php │ │ │ │ └── WebhookConfiguration.php │ │ │ ├── ValueObjects/ │ │ │ │ ├── WebhookSignature.php │ │ │ │ ├── WebhookStatus.php │ │ │ │ └── WebhookType.php │ │ │ ├── Repositories/ │ │ │ │ ├── WebhookLogRepositoryInterface.php │ │ │ │ └── WebhookConfigurationRepositoryInterface.php │ │ │ ├── Services/ │ │ │ │ ├── WebhookService.php │ │ │ │ ├── WebhookValidationService.php │ │ │ │ ├── WebhookDeliveryService.php │ │ │ │ └── WebhookSecurityService.php │ │ │ └── Events/ │ │ │ ├── WebhookReceived.php │ │ │ ├── WebhookProcessed.php │ │ │ └── WebhookFailed.php │ │ │ │ │ ├── Reconciliation/ │ │ │ ├── Entities/ │ │ │ │ ├── PaymentReconciliation.php │ │ │ │ ├── ReconciliationDetail.php │ │ │ │ └── ReconciliationReport.php │ │ │ ├── ValueObjects/ │ │ │ │ ├── ReconciliationStatus.php │ │ │ │ ├── MismatchType.php │ │ │ │ └── ReconciliationPeriod.php │ │ │ ├── Repositories/ │ │ │ │ ├── ReconciliationRepositoryInterface.php │ │ │ │ └── ReconciliationDetailRepositoryInterface.php │ │ │ ├── Services/ │ │ │ │ ├── ReconciliationService.php │ │ │ │ ├── ReconciliationReportService.php │ │ │ │ ├── MismatchResolutionService.php │ │ │ │ └── AutoReconciliationService.php │ │ │ └── Events/ │ │ │ ├── ReconciliationStarted.php │ │ │ ├── ReconciliationCompleted.php │ │ │ └── MismatchDetected.php │ │ │ │ │ └── Shared/ │ │ ├── ValueObjects/ │ │ │ ├── EntityId.php │ │ │ ├── Timestamp.php │ │ │ ├── IpAddress.php │ │ │ └── UserAgent.php │ │ ├── Contracts/ │ │ │ ├── EventPublisherInterface.php │ │ │ ├── AuditableInterface.php │ │ │ └── CacheableInterface.php │ │ ├── Traits/ │ │ │ ├── HasUuid.php │ │ │ ├── Auditable.php │ │ │ ├── SoftDeletable.php │ │ │ └── Cacheable.php │ │ └── Exceptions/ │ │ ├── DomainException.php │ │ ├── ValidationException.php │ │ └── BusinessRuleException.php │ │ │ ├── Infrastructure/ │ │ ├── Persistence/ │ │ │ ├── Repositories/ │ │ │ │ ├── Eloquent/ │ │ │ │ │ ├── EloquentProjectRepository.php │ │ │ │ │ ├── EloquentTenantRepository.php │ │ │ │ │ ├── EloquentTransactionRepository.php │ │ │ │ │ ├── EloquentWebhookLogRepository.php │ │ │ │ │ └── EloquentReconciliationRepository.php │ │ │ │ └── Cache/ │ │ │ │ ├── CachedProjectRepository.php │ │ │ │ ├── CachedTenantRepository.php │ │ │ │ └── CachedConfigurationRepository.php │ │ │ └── Models/ │ │ │ ├── Project.php │ │ │ ├── Tenant.php │ │ │ ├── TenantPaymentProvider.php │ │ │ ├── PaymentProvider.php │ │ │ ├── Transaction.php │ │ │ ├── TransactionEvent.php │ │ │ ├── WebhookLog.php │ │ │ ├── PaymentReconciliation.php │ │ │ ├── ReconciliationDetail.php │ │ │ ├── AuditLog.php │ │ │ ├── SystemConfiguration.php │ │ │ └── IdempotencyKey.php │ │ │ │ │ ├── External/ │ │ │ ├── PaymentProviders/ │ │ │ │ ├── VNPay/ │ │ │ │ │ ├── VNPayClient.php │ │ │ │ │ ├── VNPaySignature.php │ │ │ │ │ └── VNPayResponseParser.php │ │ │ │ ├── MoMo/ │ │ │ │ │ ├── MoMoClient.php │ │ │ │ │ ├── MoMoSignature.php │ │ │ │ │ └── MoMoResponseParser.php │ │ │ │ └── Common/ │ │ │ │ ├── HttpClient.php │ │ │ │ ├── SignatureValidator.php │ │ │ │ └── ResponseNormalizer.php │ │ │ └── Notifications/ │ │ │ ├── SlackNotifier.php │ │ │ ├── EmailNotifier.php │ │ │ └── SmsNotifier.php │ │ │ │ │ ├── Cache/ │ │ │ ├── RedisCache.php │ │ │ ├── CacheManager.php │ │ │ └── CacheKeys.php │ │ │ │ │ └── Queue/ │ │ ├── Jobs/ │ │ │ ├── ProcessWebhookJob.php │ │ │ ├── SendCallbackJob.php │ │ │ ├── ProcessReconciliationJob.php │ │ │ ├── SendNotificationJob.php │ │ │ └── CleanupExpiredDataJob.php │ │ └── Processors/ │ │ ├── WebhookProcessor.php │ │ ├── CallbackProcessor.php │ │ └── NotificationProcessor.php │ │ │ ├── Application/ │ │ ├── Commands/ │ │ │ ├── Project/ │ │ │ │ ├── CreateProjectCommand.php │ │ │ │ ├── UpdateProjectCommand.php │ │ │ │ └── DeleteProjectCommand.php │ │ │ ├── Tenant/ │ │ │ │ ├── CreateTenantCommand.php │ │ │ │ ├── UpdateTenantCommand.php │ │ │ │ ├── ConfigureTenantProviderCommand.php │ │ │ │ └── DeactivateTenantCommand.php │ │ │ ├── Transaction/ │ │ │ │ ├── CreateTransactionCommand.php │ │ │ │ ├── ProcessTransactionCallbackCommand.php │ │ │ │ ├── RefundTransactionCommand.php │ │ │ │ └── CancelTransactionCommand.php │ │ │ └── Webhook/ │ │ │ ├── ProcessWebhookCommand.php │ │ │ └── ResendWebhookCommand.php │ │ │ │ │ ├── Queries/ │ │ │ ├── Project/ │ │ │ │ ├── GetProjectQuery.php │ │ │ │ └── ListProjectsQuery.php │ │ │ ├── Tenant/ │ │ │ │ ├── GetTenantQuery.php │ │ │ │ └── ListTenantsQuery.php │ │ │ ├── Transaction/ │ │ │ │ ├── GetTransactionQuery.php │ │ │ │ ├── ListTransactionsQuery.php │ │ │ │ └── GetTransactionStatisticsQuery.php │ │ │ └── Reporting/ │ │ │ ├── GenerateReconciliationReportQuery.php │ │ │ ├── GenerateTransactionReportQuery.php │ │ │ └── GenerateSystemHealthReportQuery.php │ │ │ │ │ ├── Handlers/ │ │ │ ├── CommandHandlers/ │ │ │ │ ├── Project/ │ │ │ │ ├── Tenant/ │ │ │ │ ├── Transaction/ │ │ │ │ └── Webhook/ │ │ │ └── QueryHandlers/ │ │ │ ├── Project/ │ │ │ ├── Tenant/ │ │ │ ├── Transaction/ │ │ │ └── Reporting/ │ │ │ │ │ └── Services/ │ │ ├── ApplicationService.php │ │ ├── CommandBusService.php │ │ ├── QueryBusService.php │ │ └── EventBusService.php │ │ │ ├── Http/ │ │ ├── Controllers/ │ │ │ ├── API/ │ │ │ │ ├── V1/ │ │ │ │ │ ├── Admin/ │ │ │ │ │ │ ├── ProjectController.php │ │ │ │ │ │ ├── TenantController.php │ │ │ │ │ │ ├── SystemController.php │ │ │ │ │ │ ├── ReconciliationController.php │ │ │ │ │ │ └── MonitoringController.php │ │ │ │ │ ├── Client/ │ │ │ │ │ │ ├── TransactionController.php │ │ │ │ │ │ ├── PaymentController.php │ │ │ │ │ │ └── StatusController.php │ │ │ │ │ └── Webhook/ │ │ │ │ │ ├── VNPayWebhookController.php │ │ │ │ │ ├── MoMoWebhookController.php │ │ │ │ │ ├── ZaloPayWebhookController.php │ │ │ │ │ └── BaseWebhookController.php │ │ │ │ └── BaseApiController.php │ │ │ └── Web/ │ │ │ ├── DashboardController.php │ │ │ └── HealthCheckController.php │ │ │ │ │ ├── Middleware/ │ │ │ ├── Security/ │ │ │ │ ├── RateLimitMiddleware.php │ │ │ │ ├── IpWhitelistMiddleware.php │ │ │ │ ├── ValidateSignatureMiddleware.php │ │ │ │ └── CorsMiddleware.php │ │ │ ├── Request/ │ │ │ │ ├── ResolveProjectAndTenantMiddleware.php │ │ │ │ ├── ValidateRequestMiddleware.php │ │ │ │ ├── TransformRequestMiddleware.php │ │ │ │ └── IdempotencyMiddleware.php │ │ │ ├── Logging/ │ │ │ │ ├── AuditLogMiddleware.php │ │ │ │ ├── RequestLoggingMiddleware.php │ │ │ │ └── PerformanceLoggingMiddleware.php │ │ │ └── Maintenance/ │ │ │ ├── MaintenanceModeMiddleware.php │ │ │ └── FeatureToggleMiddleware.php │ │ │ │ │ ├── Requests/ │ │ │ ├── BaseRequest.php │ │ │ ├── Project/ │ │ │ │ ├── CreateProjectRequest.php │ │ │ │ ├── UpdateProjectRequest.php │ │ │ │ └── ConfigureProjectProviderRequest.php │ │ │ ├── Tenant/ │ │ │ │ ├── CreateTenantRequest.php │ │ │ │ ├── UpdateTenantRequest.php │ │ │ │ └── ConfigureTenantProviderRequest.php │ │ │ ├── Transaction/ │ │ │ │ ├── CreateTransactionRequest.php │ │ │ │ ├── QueryTransactionRequest.php │ │ │ │ ├── RefundTransactionRequest.php │ │ │ │ └── CancelTransactionRequest.php │ │ │ └── Webhook/ │ │ │ ├── VNPayWebhookRequest.php │ │ │ ├── MoMoWebhookRequest.php │ │ │ └── BaseWebhookRequest.php │ │ │ │ │ ├── Resources/ │ │ │ ├── ProjectResource.php │ │ │ ├── TenantResource.php │ │ │ ├── TransactionResource.php │ │ │ ├── PaymentProviderResource.php │ │ │ ├── ReconciliationResource.php │ │ │ └── Collections/ │ │ │ ├── ProjectCollection.php │ │ │ ├── TenantCollection.php │ │ │ └── TransactionCollection.php │ │ │ │ │ └── Responses/ │ │ ├── ApiResponse.php │ │ ├── ErrorResponse.php │ │ ├── SuccessResponse.php │ │ └── PaginatedResponse.php │ │ │ ├── Exceptions/ │ │ ├── Handler.php │ │ ├── BaseException.php │ │ ├── BusinessException.php │ │ ├── ValidationException.php │ │ ├── PaymentProviderException.php │ │ ├── WebhookException.php │ │ ├── ReconciliationException.php │ │ └── SystemException.php │ │ │ └── Providers/ │ ├── AppServiceProvider.php │ ├── RouteServiceProvider.php │ ├── EventServiceProvider.php │ ├── BroadcastServiceProvider.php │ ├── DomainServiceProvider.php │ ├── InfrastructureServiceProvider.php │ └── ApplicationServiceProvider.php │ ├── config/ │ ├── app.php │ ├── database.php │ ├── cache.php │ ├── queue.php │ ├── payment-providers.php │ ├── webhook.php │ ├── reconciliation.php │ ├── audit.php │ ├── monitoring.php │ └── security.php │ ├── database/ │ ├── migrations/ │ ├── seeders/ │ ├── factories/ │ └── schema/ │ └── smartpost_payment_service_v4.sql │ ├── routes/ │ ├── api.php │ ├── web.php │ ├── webhook.php │ └── admin.php │ ├── resources/ │ ├── views/ │ │ ├── dashboard/ │ │ ├── reports/ │ │ └── emails/ │ └── lang/ │ ├── en/ │ └── vi/ │ ├── storage/ │ ├── app/ │ │ ├── reconciliation-reports/ │ │ ├── transaction-exports/ │ │ └── audit-logs/ │ ├── framework/ │ └── logs/ │ └── tests/ ├── Unit/ │ ├── Domain/ │ ├── Application/ │ └── Infrastructure/ ├── Feature/ │ ├── API/ │ ├── Webhook/ │ └── Integration/ └── Helpers/ ``` --- ## 3. DANH SÁCH API ENDPOINTS ### 3.1. Admin APIs (Internal Management) #### 3.1.1. Project Management ``` POST /api/v1/admin/projects # Tạo dự án mới GET /api/v1/admin/projects # Danh sách dự án GET /api/v1/admin/projects/{project_id} # Chi tiết dự án PUT /api/v1/admin/projects/{project_id} # Cập nhật dự án DELETE /api/v1/admin/projects/{project_id} # Xóa dự án POST /api/v1/admin/projects/{project_id}/providers # Cấu hình cổng thanh toán cho dự án ``` #### 3.1.2. Tenant Management ``` POST /api/v1/admin/tenants # Tạo tenant mới GET /api/v1/admin/tenants # Danh sách tenant GET /api/v1/admin/tenants/{tenant_id} # Chi tiết tenant PUT /api/v1/admin/tenants/{tenant_id} # Cập nhật tenant DELETE /api/v1/admin/tenants/{tenant_id} # Xóa tenant POST /api/v1/admin/tenants/{tenant_id}/providers # Cấu hình cổng thanh toán cho tenant PUT /api/v1/admin/tenants/{tenant_id}/limits # Cập nhật hạn mức tenant ``` #### 3.1.3. System Management ``` GET /api/v1/admin/system/health # Kiểm tra sức khỏe hệ thống GET /api/v1/admin/system/metrics # Metrics hệ thống GET /api/v1/admin/system/configurations # Danh sách cấu hình hệ thống PUT /api/v1/admin/system/configurations/{key} # Cập nhật cấu hình POST /api/v1/admin/system/maintenance # Bật/tắt chế độ bảo trì ``` ### 3.2. Client APIs (Business Operations) #### 3.2.1. Transaction APIs ``` POST /api/v1/{project_code}/{app_code}/transactions # Tạo giao dịch GET /api/v1/{project_code}/{app_code}/transactions/{transaction_id} # Chi tiết giao dịch GET /api/v1/{project_code}/{app_code}/transactions/ref/{reference_id} # Tìm giao dịch theo reference POST /api/v1/{project_code}/{app_code}/transactions/{transaction_id}/refund # Hoàn tiền POST /api/v1/{project_code}/{app_code}/transactions/{transaction_id}/cancel # Hủy giao dịch GET /api/v1/{project_code}/{app_code}/transactions # Danh sách giao dịch (có filter) ``` #### 3.2.2. Payment Provider APIs ``` GET /api/v1/{project_code}/{app_code}/providers # Danh sách cổng thanh toán khả dụng GET /api/v1/{project_code}/{app_code}/providers/{provider_code} # Chi tiết cổng thanh toán ``` #### 3.2.3. Status & Health APIs ``` GET /api/v1/{project_code}/{app_code}/status # Trạng thái tenant GET /api/v1/status # Health check chung GET /api/v1/version # Thông tin version ``` ### 3.3. Webhook APIs #### 3.3.1. Payment Provider Webhooks ``` POST /api/v1/webhooks/vnpay # VNPay webhook GET /api/v1/webhooks/vnpay/return # VNPay return URL POST /api/v1/webhooks/momo # MoMo webhook GET /api/v1/webhooks/momo/return # MoMo return URL POST /api/v1/webhooks/zalopay # ZaloPay webhook GET /api/v1/webhooks/zalopay/return # ZaloPay return URL POST /api/v1/webhooks/napas # NAPAS webhook GET /api/v1/webhooks/napas/return # NAPAS return URL ``` #### 3.3.2. Generic Webhook APIs ``` POST /api/v1/webhooks/{provider_code} # Generic webhook handler POST /api/v1/webhooks/{provider_code}/test # Test webhook GET /api/v1/webhooks/logs # Webhook logs POST /api/v1/webhooks/{webhook_id}/retry # Retry webhook ``` ### 3.4. Reconciliation APIs #### 3.4.1. Reconciliation Management ``` GET /api/v1/admin/reconciliations # Danh sách đối soát POST /api/v1/admin/reconciliations # Tạo đối soát mới GET /api/v1/admin/reconciliations/{reconciliation_id} # Chi tiết đối soát PUT /api/v1/admin/reconciliations/{reconciliation_id}/resolve # Giải quyết mismatch GET /api/v1/admin/reconciliations/{reconciliation_id}/details # Chi tiết mismatch POST /api/v1/admin/reconciliations/{reconciliation_id}/export # Export báo cáo ``` ### 3.5. Monitoring & Reporting APIs #### 3.5.1. Audit & Logging ``` GET /api/v1/admin/audit-logs # Audit logs GET /api/v1/admin/webhook-logs # Webhook logs GET /api/v1/admin/transaction-logs # Transaction logs GET /api/v1/admin/system-logs # System logs ``` #### 3.5.2. Analytics & Reports ``` GET /api/v1/admin/reports/transactions # Báo cáo giao dịch GET /api/v1/admin/reports/revenue # Báo cáo doanh thu GET /api/v1/admin/reports/providers # Báo cáo theo cổng thanh toán GET /api/v1/admin/reports/tenants # Báo cáo theo tenant POST /api/v1/admin/reports/custom # Báo cáo tùy chỉnh ``` --- ## 4. CHI TIẾT API REQUEST/RESPONSE ### 4.1. Transaction Management APIs #### 4.1.1. Tạo giao dịch mới **POST** `/api/v1/{project_code}/{app_code}/transactions` **Request:** ```json { "order_id": "ORD-20250703-001", "amount": 100000, "currency": "VND", "description": "Thanh toán đơn hàng #12345", "provider_code": "vnpay", "payment_method": "card", "customer_info": { "name": "Nguyễn Văn A", "email": "nguyenvana@example.com", "phone": "0901234567", "address": "123 Đường ABC, Quận 1, TP.HCM" }, "return_url": "https://yourapp.com/payment/success", "cancel_url": "https://yourapp.com/payment/cancel", "callback_url": "https://yourapp.com/api/webhooks/payment", "expires_in_minutes": 15, "metadata": { "internal_order_id": "12345", "promotion_code": "SUMMER2025", "user_id": "USER-001" } } ``` **Response Success (201):** ```json { "success": true, "message": "Transaction created successfully", "data": { "transaction_id": "txn_7k8l9m0n1o2p3q4r5s6t", "order_id": "ORD-20250703-001", "amount": 100000, "currency": "VND", "status": "pending", "provider_transaction_id": null, "payment_url": "https://sandbox.vnpayment.vn/paymentv2/vpcpay.html?...", "expires_at": "2025-07-03T10:30:00+07:00", "created_at": "2025-07-03T10:15:00+07:00" }, "request_id": "req_1a2b3c4d5e6f7g8h9i0j" } ``` **Response Error (400):** ```json { "success": false, "error_code": "INVALID_AMOUNT", "message": "Số tiền phải lớn hơn 10,000 VND và nhỏ hơn 500,000,000 VND", "details": { "field": "amount", "value": 5000, "min_amount": 10000, "max_amount": 500000000 }, "request_id": "req_1a2b3c4d5e6f7g8h9i0j" } ``` #### 4.1.2. Lấy thông tin giao dịch **GET** `/api/v1/{project_code}/{app_code}/transactions/{transaction_id}` **Response Success (200):** ```json { "success": true, "data": { "transaction_id": "txn_7k8l9m0n1o2p3q4r5s6t", "order_id": "ORD-20250703-001", "amount": 100000, "currency": "VND", "description": "Thanh toán đơn hàng #12345", "status": "completed", "payment_method": "card", "provider_code": "vnpay", "provider_transaction_id": "14339599", "provider_order_id": "ORD20250703001", "customer_info": { "name": "Nguyễn Văn A", "email": "nguyenvana@example.com", "phone": "0901234567" }, "transaction_fee": 2300, "net_amount": 97700, "paid_at": "2025-07-03T10:18:32+07:00", "events": [ { "event_type": "created", "created_at": "2025-07-03T10:15:00+07:00", "data": {} }, { "event_type": "payment_initiated", "created_at": "2025-07-03T10:15:30+07:00", "data": { "payment_url": "https://sandbox.vnpayment.vn/..." } }, { "event_type": "paid", "created_at": "2025-07-03T10:18:32+07:00", "data": { "provider_response_code": "00", "bank_code": "NCB" } } ], "metadata": { "internal_order_id": "12345", "promotion_code": "SUMMER2025", "user_id": "USER-001" }, "created_at": "2025-07-03T10:15:00+07:00", "updated_at": "2025-07-03T10:18:32+07:00" }, "request_id": "req_2b3c4d5e6f7g8h9i0j1k" } ``` #### 4.1.3. Hoàn tiền giao dịch **POST** `/api/v1/{project_code}/{app_code}/transactions/{transaction_id}/refund` **Request:** ```json { "refund_amount": 50000, "reason": "Khách hàng yêu cầu hoàn trả một phần", "internal_refund_id": "REFUND-001", "notification_url": "https://yourapp.com/api/webhooks/refund" } ``` **Response Success (200):** ```json { "success": true, "message": "Refund initiated successfully", "data": { "refund_id": "rfn_9a8b7c6d5e4f3g2h1i0j", "transaction_id": "txn_7k8l9m0n1o2p3q4r5s6t", "refund_amount": 50000, "remaining_amount": 50000, "status": "processing", "provider_refund_id": null, "reason": "Khách hàng yêu cầu hoàn trả một phần", "created_at": "2025-07-03T14:30:00+07:00" }, "request_id": "req_3c4d5e6f7g8h9i0j1k2l" } ``` ### 4.2. Webhook Processing #### 4.2.1. VNPay Webhook **POST** `/api/v1/webhooks/vnpay` **Request từ VNPay:** ```json { "vnp_Amount": "10000000", "vnp_BankCode": "NCB", "vnp_BankTranNo": "VNP14339599", "vnp_CardType": "ATM", "vnp_OrderInfo": "Thanh toan don hang %3A ORD-20250703-001", "vnp_PayDate": "20250703101832", "vnp_ResponseCode": "00", "vnp_TmnCode": "VNPAY_TMN", "vnp_TransactionNo": "14339599", "vnp_TransactionStatus": "00", "vnp_TxnRef": "ORD20250703001", "vnp_SecureHash": "3d4f8b2a1c5e7f9d0a2b4c6e8f1a3b5c" } ``` **Response tới VNPay:** ```json { "RspCode": "00", "Message": "Confirm Success" } ``` ### 4.3. Error Responses #### 4.3.1. Standard Error Format ```json { "success": false, "error_code": "ERROR_CODE_CONSTANT", "message": "Human readable error message", "details": { "field": "field_name", "value": "invalid_value", "constraint": "validation_rule" }, "request_id": "req_unique_identifier", "timestamp": "2025-07-03T10:15:00+07:00", "trace_id": "trace_for_debugging" } ``` #### 4.3.2. Common Error Codes ```json { "SYSTEM_ERROR": "Lỗi hệ thống, vui lòng thử lại sau", "VALIDATION_ERROR": "Dữ liệu đầu vào không hợp lệ", "PROJECT_NOT_FOUND": "Không tìm thấy dự án", "TENANT_NOT_FOUND": "Không tìm thấy tenant", "TRANSACTION_NOT_FOUND": "Không tìm thấy giao dịch", "PROVIDER_NOT_CONFIGURED": "Cổng thanh toán chưa được cấu hình", "AMOUNT_INVALID": "Số tiền không hợp lệ", "TRANSACTION_EXPIRED": "Giao dịch đã hết hạn", "DUPLICATE_ORDER_ID": "Mã đơn hàng đã tồn tại", "INSUFFICIENT_BALANCE": "Số dư không đủ", "RATE_LIMIT_EXCEEDED": "Vượt quá giới hạn số lượng request", "MAINTENANCE_MODE": "Hệ thống đang bảo trì" } ``` --- ## 5. MIDDLEWARE VÀ AUTHENTICATION ### 5.1. Middleware Chain #### 5.1.1. API Request Middleware Stack ```php // Cho các API request từ client applications [ 'cors', // CORS headers 'throttle:api', // Rate limiting (100 req/min per IP) 'request.logging', // Log incoming requests 'resolve.project.tenant', // Resolve project và tenant context 'validate.project.tenant', // Validate project/tenant permissions 'idempotency', // Idempotency check 'audit.log', // Audit logging 'maintenance.check', // Check maintenance mode 'feature.toggle' // Feature toggle check ] ``` #### 5.1.2. Webhook Middleware Stack ```php // Cho webhook từ payment providers [ 'webhook.rate.limit', // Webhook-specific rate limiting 'webhook.ip.whitelist', // IP whitelist validation 'webhook.signature.validation', // Signature verification 'webhook.logging', // Log webhook requests 'webhook.idempotency', // Prevent duplicate webhook processing 'audit.webhook' // Webhook audit logging ] ``` #### 5.1.3. Admin Middleware Stack ```php // Cho admin/management APIs [ 'cors', 'throttle:admin', // Higher rate limit for admin (1000 req/min) 'admin.ip.whitelist', // Admin IP whitelist 'request.logging', 'audit.admin', // Admin action logging 'maintenance.bypass' // Allow admin during maintenance ] ``` ### 5.2. Middleware Implementation Details #### 5.2.1. ResolveProjectAndTenantMiddleware ```php

projectRepository = $projectRepository; $this->tenantRepository = $tenantRepository; } public function handle(Request $request, Closure $next) { $projectCode = $request->route('project_code'); $appCode = $request->route('app_code'); if (!$projectCode || !$appCode) { return response()->json([ 'success' => false, 'error_code' => 'MISSING_PROJECT_OR_APP_CODE', 'message' => 'Project code và app code là bắt buộc' ], 400); } // Resolve project $project = $this->projectRepository->findByCode($projectCode); if (!$project) { throw new ProjectNotFoundException("Project not found: {$projectCode}"); } if (!$project->isActive()) { return response()->json([ 'success' => false, 'error_code' => 'PROJECT_INACTIVE', 'message' => 'Dự án đã bị vô hiệu hóa' ], 403); } // Resolve tenant $tenant = $this->tenantRepository->findByAppCode($appCode, $project->getId()); if (!$tenant) { throw new TenantNotFoundException("Tenant not found: {$appCode}"); } if (!$tenant->isActive()) { return response()->json([ 'success' => false, 'error_code' => 'TENANT_INACTIVE', 'message' => 'Tenant đã bị vô hiệu hóa' ], 403); } // Add to request context $request->merge([ 'resolved_project' => $project, 'resolved_tenant' => $tenant, 'project_id' => $project->getId(), 'tenant_id' => $tenant->getId() ]); // Add to app context for easy access app()->instance('current.project', $project); app()->instance('current.tenant', $tenant); return $next($request); } } ``` #### 5.2.2. AuditLogMiddleware ```php

auditService = $auditService; } public function handle(Request $request, Closure $next) { $startTime = microtime(true); $requestId = Str::uuid(); // Add request ID to request for tracking $request->headers->set('X-Request-ID', $requestId); // Log request start $this->auditService->logRequest([ 'request_id' => $requestId, 'entity_type' => $this->getEntityType($request), 'action' => $this->getAction($request), 'endpoint' => $request->path(), 'method' => $request->method(), 'ip_address' => $request->ip(), 'user_agent' => $request->userAgent(), 'project_code' => $request->route('project_code'), 'app_code' => $request->route('app_code'), 'tenant_id' => $request->get('tenant_id'), 'request_headers' => $this->sanitizeHeaders($request->headers->all()), 'request_body' => $this->sanitizeRequestBody($request->all()), 'received_at' => now() ]); // Process request $response = $next($request); // Calculate response time $responseTime = round((microtime(true) - $startTime) * 1000); // Log response $this->auditService->logResponse([ 'request_id' => $requestId, 'response_status' => $response->getStatusCode(), 'response_body' => $this->sanitizeResponseBody($response), 'response_time_ms' => $responseTime, 'completed_at' => now() ]); // Add request ID to response headers $response->headers->set('X-Request-ID', $requestId); return $response; } private function getEntityType(Request $request): string { $path = $request->path(); if (str_contains($path, 'transactions')) return 'transaction'; if (str_contains($path, 'projects')) return 'project'; if (str_contains($path, 'tenants')) return 'tenant'; if (str_contains($path, 'webhooks')) return 'webhook'; if (str_contains($path, 'reconciliations')) return 'reconciliation'; return 'unknown'; } private function getAction(Request $request): string { $method = $request->method(); $path = $request->path(); return match($method) { 'GET' => str_contains($path, '/') && !str_ends_with($path, 's') ? 'view' : 'list', 'POST' => 'create', 'PUT', 'PATCH' => 'update', 'DELETE' => 'delete', default => 'unknown' }; } private function sanitizeHeaders(array $headers): array { $sensitive = ['authorization', 'cookie', 'x-api-key', 'x-signature']; $sanitized = []; foreach ($headers as $key => $value) { if (in_array(strtolower($key), $sensitive)) { $sanitized[$key] = '[REDACTED]'; } else { $sanitized[$key] = is_array($value) ? $value[0] : $value; } } return $sanitized; } private function sanitizeRequestBody(array $body): array { $sensitive = [ 'password', 'secret_key', 'api_key', 'secret', 'token', 'webhook_secret', 'signature', 'hash', 'private_key' ]; return $this->recursiveSanitize($body, $sensitive); } private function sanitizeResponseBody($response): ?array { if (!$response instanceof \Illuminate\Http\JsonResponse) { return null; } $data = $response->getData(true); // Don't log large response bodies if (json_encode($data) && strlen(json_encode($data)) > 10000) { return ['message' => 'Response body too large to log']; } $sensitive = ['secret_key', 'api_key', 'token', 'signature']; return $this->recursiveSanitize($data, $sensitive); } private function recursiveSanitize(array $data, array $sensitive): array { $sanitized = []; foreach ($data as $key => $value) { if (in_array(strtolower($key), $sensitive)) { $sanitized[$key] = '[REDACTED]'; } elseif (is_array($value)) { $sanitized[$key] = $this->recursiveSanitize($value, $sensitive); } else { $sanitized[$key] = $value; } } return $sanitized; } } ``` #### 5.2.3. IdempotencyMiddleware ```php

idempotencyService = $idempotencyService; } public function handle(Request $request, Closure $next) { // Only check for POST, PUT, PATCH requests if (!in_array($request->method(), ['POST', 'PUT', 'PATCH'])) { return $next($request); } $idempotencyKey = $request->header('X-Idempotency-Key'); if (!$idempotencyKey) { // Generate one for internal use $idempotencyKey = $this->generateIdempotencyKey($request); } // Check if this request was already processed $existingResult = $this->idempotencyService->getResult( $idempotencyKey, $request->get('tenant_id') ); if ($existingResult) { // Return the cached result return response()->json($existingResult['response'], $existingResult['status_code']) ->withHeaders(['X-Idempotency-Cache' => 'HIT']); } // Process the request $response = $next($request); // Cache the result for successful operations if ($response->getStatusCode() < 400) { $this->idempotencyService->storeResult( $idempotencyKey, $request->get('tenant_id'), $response->getData(true), $response->getStatusCode(), now()->addHours(24) // Cache for 24 hours ); } return $response->withHeaders(['X-Idempotency-Cache' => 'MISS']); } private function generateIdempotencyKey(Request $request): string { $components = [ $request->method(), $request->path(), $request->get('tenant_id'), md5(json_encode($request->all())) ]; return 'auto_' . md5(implode('|', $components)); } } ``` --- ## 6. XỬ LÝ WEBHOOK ### 6.1. Webhook Security #### 6.1.1. Multi-layer Validation ```php

webhookSecurity = $webhookSecurity; } public function handle(Request $request, Closure $next) { $providerCode = $request->route('provider') ?? $this->detectProvider($request); if (!$providerCode) { return response()->json([ 'error' => 'Provider not detected' ], 400); } // Step 1: IP Whitelist validation if (!$this->webhookSecurity->validateIP($providerCode, $request->ip())) { \Log::warning('Webhook IP validation failed', [ 'provider' => $providerCode, 'ip' => $request->ip(), 'user_agent' => $request->userAgent() ]); return response()->json(['error' => 'Invalid source'], 403); } // Step 2: Signature validation if (!$this->webhookSecurity->validateSignature($providerCode, $request)) { \Log::warning('Webhook signature validation failed', [ 'provider' => $providerCode, 'ip' => $request->ip(), 'headers' => $request->headers->all() ]); return response()->json(['error' => 'Invalid signature'], 403); } // Step 3: Timestamp validation (prevent replay attacks) if (!$this->webhookSecurity->validateTimestamp($request)) { \Log::warning('Webhook timestamp validation failed', [ 'provider' => $providerCode, 'timestamp' => $request->header('timestamp') ]); return response()->json(['error' => 'Request too old'], 403); } $request->merge(['validated_provider' => $providerCode]); return $next($request); } private function detectProvider(Request $request): ?string { $path = $request->path(); $userAgent = $request->userAgent(); if (str_contains($path, 'vnpay')) return 'vnpay'; if (str_contains($path, 'momo')) return 'momo'; if (str_contains($path, 'zalopay')) return 'zalopay'; if (str_contains($path, 'napas')) return 'napas'; // Detect by User-Agent if (str_contains($userAgent, 'VNPay')) return 'vnpay'; if (str_contains($userAgent, 'MoMo')) return 'momo'; return null; } } ``` ### 6.2. Webhook Processing #### 6.2.1. Generic Webhook Controller ```php

webhookService = $webhookService; } public function handle(Request $request, string $provider) { try { // Log incoming webhook $webhookLog = $this->webhookService->logIncomingWebhook([ 'provider_code' => $provider, 'source_ip' => $request->ip(), 'headers' => $request->headers->all(), 'payload' => $request->all(), 'received_at' => now() ]); // Process webhook asynchronously ProcessWebhookJob::dispatch($webhookLog->id, $provider, $request->all()) ->onQueue('webhooks'); // Return immediate response to provider return $this->getProviderSuccessResponse($provider); } catch (\Exception $e) { \Log::error('Webhook processing error', [ 'provider' => $provider, 'error' => $e->getMessage(), 'payload' => $request->all() ]); return $this->getProviderErrorResponse($provider, $e->getMessage()); } } private function getProviderSuccessResponse(string $provider): \Illuminate\Http\JsonResponse { return match($provider) { 'vnpay' => response()->json(['RspCode' => '00', 'Message' => 'Confirm Success']), 'momo' => response()->json(['status' => 0, 'message' => 'success']), 'zalopay' => response()->json(['return_code' => 1, 'return_message' => 'success']), 'napas' => response()->json(['responseCode' => '00', 'desc' => 'success']), default => response()->json(['status' => 'success']) }; } private function getProviderErrorResponse(string $provider, string $error): \Illuminate\Http\JsonResponse { return match($provider) { 'vnpay' => response()->json(['RspCode' => '99', 'Message' => 'Error'], 500), 'momo' => response()->json(['status' => -1, 'message' => 'error']), 'zalopay' => response()->json(['return_code' => 0, 'return_message' => 'error']), 'napas' => response()->json(['responseCode' => '99', 'desc' => 'error']), default => response()->json(['status' => 'error', 'message' => $error], 500) }; } } ``` #### 6.2.2. Webhook Processing Job ```php

webhookLogId = $webhookLogId; $this->provider = $provider; $this->payload = $payload; } public function handle( WebhookService $webhookService, TransactionService $transactionService ) { try { // Update webhook log status $webhookService->updateStatus($this->webhookLogId, 'processing'); // Process based on provider $result = match($this->provider) { 'vnpay' => $this->processVNPayWebhook($transactionService), 'momo' => $this->processMoMoWebhook($transactionService), 'zalopay' => $this->processZaloPayWebhook($transactionService), 'napas' => $this->processNapasWebhook($transactionService), default => throw new \Exception("Unsupported provider: {$this->provider}") }; // Update webhook log with result $webhookService->updateStatus($this->webhookLogId, 'processed', $result); // Send callback to tenant if needed if ($result['transaction_id'] && $result['callback_url']) { SendCallbackJob::dispatch( $result['transaction_id'], $result['callback_url'], $result['callback_data'] )->onQueue('callbacks'); } } catch (\Exception $e) { $webhookService->updateStatus($this->webhookLogId, 'failed', [ 'error' => $e->getMessage(), 'trace' => $e->getTraceAsString() ]); throw $e; // Re-throw for retry mechanism } } private function processVNPayWebhook(TransactionService $transactionService): array { $transactionRef = $this->payload['vnp_TxnRef'] ?? null; $responseCode = $this->payload['vnp_ResponseCode'] ?? null; $transactionNo = $this->payload['vnp_TransactionNo'] ?? null; $amount = $this->payload['vnp_Amount'] ?? null; if (!$transactionRef) { throw new \Exception('Missing vnp_TxnRef in webhook'); } // Find transaction by provider reference $transaction = $transactionService->findByProviderReference($transactionRef); if (!$transaction) { throw new \Exception("Transaction not found: {$transactionRef}"); } // Update transaction based on response code $status = $responseCode === '00' ? 'completed' : 'failed'; $updateData = [ 'status' => $status, 'provider_transaction_id' => $transactionNo, 'provider_response' => $this->payload, 'processed_at' => now() ]; if ($status === 'completed') { $updateData['paid_at'] = now(); $updateData['net_amount'] = ($amount / 100) - $transaction->transaction_fee; } else { $updateData['failed_at'] = now(); $updateData['failure_reason'] = $this->getVNPayErrorMessage($responseCode); } $transactionService->updateTransaction($transaction->id, $updateData); return [ 'transaction_id' => $transaction->id, 'status' => $status, 'callback_url' => $transaction->callback_url, 'callback_data' => [ 'transaction_id' => $transaction->id, 'order_id' => $transaction->order_id, 'status' => $status, 'amount' => $transaction->amount, 'provider_transaction_id' => $transactionNo, 'processed_at' => now()->toISOString() ] ]; } private function getVNPayErrorMessage(string $responseCode): string { return match($responseCode) { '07' => 'Trừ tiền thành công. Giao dịch bị nghi ngờ (liên quan tới lừa đảo, giao dịch bất thường).', '09' => 'Giao dịch không thành công do: Thẻ/Tài khoản của khách hàng chưa đăng ký dịch vụ InternetBanking tại ngân hàng.', '10' => 'Giao dịch không thành công do: Khách hàng xác thực thông tin thẻ/tài khoản không đúng quá 3 lần', '11' => 'Giao dịch không thành công do: Đã hết hạn chờ thanh toán. Xin quý khách vui lòng thực hiện lại giao dịch.', '12' => 'Giao dịch không thành công do: Thẻ/Tài khoản của khách hàng bị khóa.', '13' => 'Giao dịch không thành công do Quý khách nhập sai mật khẩu xác thực giao dịch (OTP).', '24' => 'Giao dịch không thành công do: Khách hàng hủy giao dịch', '51' => 'Giao dịch không thành công do: Tài khoản của quý khách không đủ số dư để thực hiện giao dịch.', '65' => 'Giao dịch không thành công do: Tài khoản của Quý khách đã vượt quá hạn mức giao dịch trong ngày.', '75' => 'Ngân hàng thanh toán đang bảo trì.', '79' => 'Giao dịch không thành công do: KH nhập sai mật khẩu thanh toán quá số lần quy định.', '99' => 'Các lỗi khác (lỗi còn lại, không có trong danh sách mã lỗi đã liệt kê)', default => "Giao dịch thất bại với mã lỗi: {$responseCode}" }; } } ``` --- ## 7. XỬ LÝ CÁC TRƯỜNG HỢP ĐẶC BIỆT ### 7.1. Fraud Detection #### 7.1.1. Real-time Fraud Detection Service ```php

initializeRules(); } public function analyzeTransaction(Transaction $transaction): array { $riskScore = 0; $flags = []; foreach ($this->rules as $rule) { $result = $rule->analyze($transaction); $riskScore += $result['score']; if ($result['triggered']) { $flags[] = $result['flag']; } } $riskLevel = $this->calculateRiskLevel($riskScore); if ($riskLevel === 'HIGH') { event(new FraudDetected($transaction, $riskScore, $flags)); } return [ 'risk_score' => $riskScore, 'risk_level' => $riskLevel, 'flags' => $flags, 'action' => $this->getRecommendedAction($riskLevel) ]; } private function initializeRules(): void { $this->rules = [ new VelocityRule(), // Tần suất giao dịch new AmountRule(), // Số tiền bất thường new GeolocationRule(), // Địa lý bất thường new DeviceFingerprintRule(), // Thiết bị lạ new TimePatternRule(), // Thời gian bất thường new BehaviorRule() // Hành vi bất thường ]; } private function calculateRiskLevel(int $score): string { return match(true) { $score >= 80 => 'HIGH', $score >= 50 => 'MEDIUM', $score >= 20 => 'LOW', default => 'MINIMAL' }; } private function getRecommendedAction(string $riskLevel): string { return match($riskLevel) { 'HIGH' => 'BLOCK', 'MEDIUM' => 'REVIEW', 'LOW' => 'MONITOR', default => 'ALLOW' }; } } ``` ### 7.2. Circuit Breaker Pattern #### 7.2.2. Payment Provider Circuit Breaker ```php

cache = $cache; $this->failureThreshold = $failureThreshold; $this->recoveryTimeout = $recoveryTimeout; $this->monitoringPeriod = $monitoringPeriod; } public function call(string $service, callable $callback) { $state = $this->getState($service); if ($state === 'OPEN') { if ($this->shouldAttemptReset($service)) { $this->setState($service, 'HALF_OPEN'); } else { throw new \Exception("Circuit breaker is OPEN for service: {$service}"); } } try { $result = $callback(); $this->recordSuccess($service); if ($state === 'HALF_OPEN') { $this->setState($service, 'CLOSED'); } return $result; } catch (\Exception $e) { $this->recordFailure($service); if ($this->shouldOpenCircuit($service)) { $this->setState($service, 'OPEN'); $this->setLastFailureTime($service, time()); } throw $e; } } private function getState(string $service): string { return $this->cache->get("circuit_breaker:{$service}:state", 'CLOSED'); } private function setState(string $service, string $state): void { $this->cache->put("circuit_breaker:{$service}:state", $state, $this->monitoringPeriod); } private function recordFailure(string $service): void { $key = "circuit_breaker:{$service}:failures"; $failures = $this->cache->get($key, 0); $this->cache->put($key, $failures + 1, $this->monitoringPeriod); } private function recordSuccess(string $service): void { $this->cache->forget("circuit_breaker:{$service}:failures"); } private function shouldOpenCircuit(string $service): bool { $failures = $this->cache->get("circuit_breaker:{$service}:failures", 0); return $failures >= $this->failureThreshold; } private function shouldAttemptReset(string $service): bool { $lastFailure = $this->cache->get("circuit_breaker:{$service}:last_failure", 0); return (time() - $lastFailure) >= $this->recoveryTimeout; } private function setLastFailureTime(string $service, int $time): void { $this->cache->put("circuit_breaker:{$service}:last_failure", $time, $this->recoveryTimeout * 2); } } ``` ### 7.3. Retry Mechanism #### 7.3.1. Exponential Backoff Retry ```php

maxAttempts = $maxAttempts; $this->baseDelay = $baseDelay; $this->maxDelay = $maxDelay; $this->multiplier = $multiplier; } public function execute(callable $callback, array $retryableExceptions = []) { $attempt = 1; $lastException = null; while ($attempt <= $this->maxAttempts) { try { return $callback(); } catch (\Exception $e) { $lastException = $e; // Check if this exception is retryable if (!$this->isRetryable($e, $retryableExceptions)) { throw $e; } // Don't retry on last attempt if ($attempt === $this->maxAttempts) { break; } // Calculate delay with jitter $delay = $this->calculateDelay($attempt); $this->sleep($delay); \Log::warning('Retrying operation', [ 'attempt' => $attempt, 'max_attempts' => $this->maxAttempts, 'delay_ms' => $delay, 'exception' => $e->getMessage() ]); $attempt++; } } throw $lastException; } private function isRetryable(\Exception $e, array $retryableExceptions): bool { if (empty($retryableExceptions)) { // Default retryable exceptions return $e instanceof \GuzzleHttp\Exception\ConnectException || $e instanceof \GuzzleHttp\Exception\ServerException || $e instanceof \Illuminate\Http\Client\ConnectionException; } foreach ($retryableExceptions as $exceptionClass) { if ($e instanceof $exceptionClass) { return true; } } return false; } private function calculateDelay(int $attempt): int { // Exponential backoff with jitter $exponentialDelay = $this->baseDelay * pow($this->multiplier, $attempt - 1); $delayWithJitter = $exponentialDelay + random_int(0, (int)($exponentialDelay * 0.1)); return min($delayWithJitter, $this->maxDelay); } private function sleep(int $milliseconds): void { usleep($milliseconds * 1000); } } ``` --- ## 8. AUDIT LOG VÀ MONITORING ### 8.1. Comprehensive Audit System #### 8.1.1. Audit Log Service ```php

\Str::uuid(), 'tenant_id' => $data['tenant_id'] ?? null, 'entity_type' => $data['entity_type'], 'entity_id' => $data['entity_id'], 'action' => $data['action'], 'old_values' => $data['old_values'] ?? null, 'new_values' => $data['new_values'] ?? null, 'changed_fields' => $data['changed_fields'] ?? null, 'user_id' => $data['user_id'] ?? 'system', 'user_type' => $data['user_type'] ?? 'system', 'ip_address' => $data['ip_address'] ?? request()->ip(), 'user_agent' => $data['user_agent'] ?? request()->userAgent(), 'request_id' => $data['request_id'] ?? request()->header('X-Request-ID'), 'additional_data' => $data['additional_data'] ?? null, 'created_at' => now() ]); } public function logEntityChange( string $entityType, string $entityId, string $action, array $oldValues = null, array $newValues = null, string $tenantId = null ): AuditLog { $changedFields = null; if ($oldValues && $newValues) { $changedFields = array_keys(array_diff_assoc($newValues, $oldValues)); } return $this->logAction([ 'tenant_id' => $tenantId, 'entity_type' => $entityType, 'entity_id' => $entityId, 'action' => $action, 'old_values' => $oldValues, 'new_values' => $newValues, 'changed_fields' => $changedFields ]); } public function logTransactionEvent( string $transactionId, string $eventType, array $eventData = [], string $source = 'system' ): AuditLog { return $this->logAction([ 'tenant_id' => app('current.tenant')->id ?? null, 'entity_type' => 'transaction', 'entity_id' => $transactionId, 'action' => $eventType, 'new_values' => $eventData, 'additional_data' => [ 'source' => $source, 'timestamp' => now()->toISOString() ] ]); } public function logWebhookEvent( string $webhookId, string $provider, array $payload, string $status = 'received' ): AuditLog { return $this->logAction([ 'entity_type' => 'webhook', 'entity_id' => $webhookId, 'action' => "webhook_{$status}", 'new_values' => [ 'provider' => $provider, 'payload_hash' => md5(json_encode($payload)), 'status' => $status ], 'additional_data' => [ 'provider' => $provider, 'payload_size' => strlen(json_encode($payload)) ] ]); } } ``` ### 8.2. System Health Monitoring #### 8.2.1. Health Check Service ```php

$this->getOverallStatus(), 'timestamp' => now()->toISOString(), 'version' => config('app.version'), 'environment' => config('app.env'), 'checks' => [ 'database' => $this->checkDatabase(), 'redis' => $this->checkRedis(), 'queue' => $this->checkQueue(), 'storage' => $this->checkStorage(), 'payment_providers' => $this->checkPaymentProviders(), 'external_apis' => $this->checkExternalAPIs() ] ]; } private function checkDatabase(): array { try { $start = microtime(true); DB::select('SELECT 1'); $responseTime = round((microtime(true) - $start) * 1000, 2); return [ 'status' => 'healthy', 'response_time_ms' => $responseTime, 'connection' => DB::connection()->getName() ]; } catch (\Exception $e) { return [ 'status' => 'unhealthy', 'error' => $e->getMessage() ]; } } private function checkRedis(): array { try { $start = microtime(true); Redis::ping(); $responseTime = round((microtime(true) - $start) * 1000, 2); return [ 'status' => 'healthy', 'response_time_ms' => $responseTime, 'memory_usage' => Redis::info('memory')['used_memory_human'] ?? 'unknown' ]; } catch (\Exception $e) { return [ 'status' => 'unhealthy', 'error' => $e->getMessage() ]; } } private function checkQueue(): array { try { $queueSizes = [ 'default' => \Queue::size('default'), 'webhooks' => \Queue::size('webhooks'), 'callbacks' => \Queue::size('callbacks'), 'reconciliation' => \Queue::size('reconciliation') ]; $totalPending = array_sum($queueSizes); $status = $totalPending > 1000 ? 'warning' : 'healthy'; return [ 'status' => $status, 'queue_sizes' => $queueSizes, 'total_pending' => $totalPending ]; } catch (\Exception $e) { return [ 'status' => 'unhealthy', 'error' => $e->getMessage() ]; } } private function checkPaymentProviders(): array { $providers = ['vnpay', 'momo', 'zalopay', 'napas']; $results = []; foreach ($providers as $provider) { $results[$provider] = $this->checkProviderHealth($provider); } return $results; } private function checkProviderHealth(string $provider): array { try { // Test với health check endpoint nếu có $start = microtime(true); switch ($provider) { case 'vnpay': // VNPay không có health check endpoint public // Kiểm tra connectivity thông qua DNS resolution $host = parse_url(config("payment-providers.{$provider}.api_url"), PHP_URL_HOST); if (!gethostbyname($host)) { throw new \Exception('Cannot resolve hostname'); } break; default: // Generic connectivity check $host = parse_url(config("payment-providers.{$provider}.api_url"), PHP_URL_HOST); if (!gethostbyname($host)) { throw new \Exception('Cannot resolve hostname'); } break; } $responseTime = round((microtime(true) - $start) * 1000, 2); return [ 'status' => 'healthy', 'response_time_ms' => $responseTime ]; } catch (\Exception $e) { return [ 'status' => 'unhealthy', 'error' => $e->getMessage() ]; } } private function getOverallStatus(): string { $checks = [ $this->checkDatabase()['status'], $this->checkRedis()['status'], $this->checkQueue()['status'] ]; if (in_array('unhealthy', $checks)) { return 'unhealthy'; } if (in_array('warning', $checks)) { return 'warning'; } return 'healthy'; } } ``` --- ## 9. JOBS VÀ QUEUE PROCESSING ### 9.1. Queue Configuration #### 9.1.1. Queue Structure ```php // config/queue.php return [ 'default' => env('QUEUE_CONNECTION', 'redis'), 'connections' => [ 'redis' => [ 'driver' => 'redis', 'connection' => 'default', 'queue' => 'default', 'retry_after' => 90, 'block_for' => null, ], ], 'batching' => [ 'database' => env('DB_CONNECTION', 'mysql'), 'table' => 'job_batches', ], // Custom queue definitions 'queues' => [ 'high_priority' => ['webhooks', 'callbacks'], 'medium_priority' => ['reconciliation', 'notifications'], 'low_priority' => ['reports', 'cleanup'] ] ]; ``` ### 9.2. Critical Jobs #### 9.2.1. SendCallbackJob ```php

transactionId = $transactionId; $this->callbackUrl = $callbackUrl; $this->callbackData = $callbackData; $this->attempt = $attempt; // Set queue based on priority $this->onQueue('callbacks'); } public function handle( TransactionRepositoryInterface $transactionRepository, AuditLogService $auditService, RetryHandler $retryHandler ) { $transaction = $transactionRepository->findById($this->transactionId); if (!$transaction) { \Log::error('Transaction not found for callback', [ 'transaction_id' => $this->transactionId ]); return; } try { // Log callback attempt $auditService->logAction([ 'tenant_id' => $transaction->tenant_id, 'entity_type' => 'transaction_callback', 'entity_id' => $this->transactionId, 'action' => 'callback_attempt', 'additional_data' => [ 'callback_url' => $this->callbackUrl, 'attempt' => $this->attempt, 'max_attempts' => $this->tries ] ]); // Send callback with retry mechanism $response = $retryHandler->execute(function() { return Http::timeout($this->timeout) ->withHeaders([ 'Content-Type' => 'application/json', 'User-Agent' => 'SmartPost-Payment-Service/1.0', 'X-Callback-Signature' => $this->generateSignature(), 'X-Callback-Timestamp' => now()->timestamp, 'X-Callback-Attempt' => $this->attempt ]) ->post($this->callbackUrl, $this->callbackData); }); // Check response if ($response->successful()) { $auditService->logAction([ 'tenant_id' => $transaction->tenant_id, 'entity_type' => 'transaction_callback', 'entity_id' => $this->transactionId, 'action' => 'callback_success', 'additional_data' => [ 'response_status' => $response->status(), 'response_time_ms' => $response->handlerStats()['total_time'] * 1000, 'attempt' => $this->attempt ] ]); // Update transaction callback status $transactionRepository->updateCallback($this->transactionId, [ 'callback_status' => 'delivered', 'callback_delivered_at' => now(), 'callback_attempts' => $this->attempt, 'callback_response' => [ 'status' => $response->status(), 'body' => $response->body() ] ]); } else { throw new \Exception("HTTP {$response->status()}: {$response->body()}"); } } catch (\Exception $e) { \Log::error('Callback delivery failed', [ 'transaction_id' => $this->transactionId, 'callback_url' => $this->callbackUrl, 'attempt' => $this->attempt, 'error' => $e->getMessage() ]); $auditService->logAction([ 'tenant_id' => $transaction->tenant_id, 'entity_type' => 'transaction_callback', 'entity_id' => $this->transactionId, 'action' => 'callback_failed', 'additional_data' => [ 'error' => $e->getMessage(), 'attempt' => $this->attempt, 'will_retry' => $this->attempt < $this->tries ] ]); // Update callback status on final failure if ($this->attempt >= $this->tries) { $transactionRepository->updateCallback($this->transactionId, [ 'callback_status' => 'failed', 'callback_failed_at' => now(), 'callback_attempts' => $this->attempt, 'callback_error' => $e->getMessage() ]); } throw $e; // Re-throw for retry mechanism } } public function failed(\Exception $exception) { \Log::error('Callback job finally failed', [ 'transaction_id' => $this->transactionId, 'callback_url' => $this->callbackUrl, 'attempts' => $this->tries, 'error' => $exception->getMessage() ]); // Send alert to monitoring system \Event::dispatch(new \App\Events\CallbackDeliveryFailed( $this->transactionId, $this->callbackUrl, $exception->getMessage() )); } private function generateSignature(): string { $secret = config('app.callback_secret'); $payload = json_encode($this->callbackData); return hash_hmac('sha256', $payload, $secret); } } ``` #### 9.2.2. ProcessReconciliationJob ```php

tenantId = $tenantId; $this->providerId = $providerId; $this->reconciliationDate = $reconciliationDate; $this->onQueue('reconciliation'); } public function handle(ReconciliationService $reconciliationService) { try { \Log::info('Starting reconciliation process', [ 'tenant_id' => $this->tenantId, 'provider_id' => $this->providerId, 'date' => $this->reconciliationDate->toDateString() ]); $result = $reconciliationService->reconcileForTenant( $this->tenantId, $this->providerId, $this->reconciliationDate ); \Log::info('Reconciliation completed', [ 'tenant_id' => $this->tenantId, 'provider_id' => $this->providerId, 'result' => $result ]); // Send notification if there are mismatches if ($result['mismatched_transactions'] > 0) { \Event::dispatch(new \App\Events\ReconciliationMismatchDetected( $this->tenantId, $this->providerId, $result )); } } catch (\Exception $e) { \Log::error('Reconciliation process failed', [ 'tenant_id' => $this->tenantId, 'provider_id' => $this->providerId, 'error' => $e->getMessage() ]); throw $e; } } } ``` #### 9.2.3. CleanupExpiredDataJob ```php

onQueue('cleanup'); } public function handle(DataRetentionService $dataRetentionService) { try { \Log::info('Starting data cleanup process'); $results = [ 'audit_logs' => $dataRetentionService->cleanupAuditLogs(), 'webhook_logs' => $dataRetentionService->cleanupWebhookLogs(), 'transaction_events' => $dataRetentionService->cleanupTransactionEvents(), 'idempotency_keys' => $dataRetentionService->cleanupIdempotencyKeys(), 'expired_transactions' => $dataRetentionService->cleanupExpiredTransactions() ]; \Log::info('Data cleanup completed', $results); } catch (\Exception $e) { \Log::error('Data cleanup failed', [ 'error' => $e->getMessage() ]); throw $e; } } } ``` --- ## 10. ERROR HANDLING ### 10.1. Exception Hierarchy #### 10.1.1. Base Exception Classes ```php

errorCode = $errorCode ?: static::getDefaultErrorCode(); $this->errorData = $errorData; $this->httpStatusCode = $httpStatusCode; } abstract protected static function getDefaultErrorCode(): string; public function getErrorCode(): string { return $this->errorCode; } public function getErrorData(): array { return $this->errorData; } public function getHttpStatusCode(): int { return $this->httpStatusCode; } public function toArray(): array { return [ 'success' => false, 'error_code' => $this->getErrorCode(), 'message' => $this->getMessage(), 'details' => $this->getErrorData(), 'timestamp' => now()->toISOString() ]; } } class BusinessException extends BaseException { protected static function getDefaultErrorCode(): string { return 'BUSINESS_RULE_VIOLATION'; } } class ValidationException extends BaseException { protected static function getDefaultErrorCode(): string { return 'VALIDATION_ERROR'; } } class PaymentProviderException extends BaseException { protected static function getDefaultErrorCode(): string { return 'PAYMENT_PROVIDER_ERROR'; } } ``` ### 10.2. Global Exception Handler #### 10.2.1. Custom Exception Handler ```php

reportable(function (Throwable $e) { if (app()->bound('sentry')) { app('sentry')->captureException($e); } }); } public function render($request, Throwable $e) { // API requests should always return JSON if ($request->expectsJson() || $request->is('api/*')) { return $this->renderApiException($request, $e); } return parent::render($request, $e); } private function renderApiException(Request $request, Throwable $e): JsonResponse { // Handle custom exceptions if ($e instanceof BaseException) { return response()->json($e->toArray(), $e->getHttpStatusCode()); } // Handle Laravel validation exceptions if ($e instanceof LaravelValidationException) { return response()->json([ 'success' => false, 'error_code' => 'VALIDATION_ERROR', 'message' => 'Dữ liệu đầu vào không hợp lệ', 'details' => [ 'errors' => $e->errors() ], 'timestamp' => now()->toISOString() ], 422); } // Handle 404 errors if ($e instanceof NotFoundHttpException) { return response()->json([ 'success' => false, 'error_code' => 'NOT_FOUND', 'message' => 'Không tìm thấy tài nguyên được yêu cầu', 'timestamp' => now()->toISOString() ], 404); } // Handle 405 errors if ($e instanceof MethodNotAllowedHttpException) { return response()->json([ 'success' => false, 'error_code' => 'METHOD_NOT_ALLOWED', 'message' => 'Phương thức HTTP không được hỗ trợ', 'timestamp' => now()->toISOString() ], 405); } // Handle generic exceptions $statusCode = method_exists($e, 'getStatusCode') ? $e->getStatusCode() : 500; $errorCode = $statusCode === 500 ? 'INTERNAL_SERVER_ERROR' : 'CLIENT_ERROR'; $response = [ 'success' => false, 'error_code' => $errorCode, 'message' => $statusCode === 500 ? 'Đã xảy ra lỗi hệ thống' : $e->getMessage(), 'timestamp' => now()->toISOString() ]; // Add debug info in non-production environments if (config('app.debug') && $statusCode === 500) { $response['debug'] = [ 'exception' => get_class($e), 'file' => $e->getFile(), 'line' => $e->getLine(), 'trace' => $e->getTraceAsString() ]; } // Add request ID for tracking if ($requestId = $request->header('X-Request-ID')) { $response['request_id'] = $requestId; } return response()->json($response, $statusCode); } protected function context() { return array_merge(parent::context(), [ 'tenant_id' => app('current.tenant')->id ?? null, 'project_id' => app('current.project')->id ?? null, 'request_id' => request()->header('X-Request-ID') ]); } } ``` ### 10.3. Error Code Registry #### 10.3.1. Centralized Error Codes ```php

'Đã xảy ra lỗi hệ thống, vui lòng thử lại sau', self::DATABASE_ERROR => 'Lỗi kết nối cơ sở dữ liệu', self::NETWORK_ERROR => 'Lỗi kết nối mạng', self::CACHE_ERROR => 'Lỗi hệ thống cache', self::QUEUE_ERROR => 'Lỗi hệ thống queue', // Authentication & Authorization self::UNAUTHORIZED => 'Không có quyền truy cập', self::FORBIDDEN => 'Truy cập bị từ chối', self::INVALID_API_KEY => 'API key không hợp lệ', self::RATE_LIMIT_EXCEEDED => 'Vượt quá giới hạn số lượng request', // Validation self::VALIDATION_ERROR => 'Dữ liệu đầu vào không hợp lệ', self::INVALID_AMOUNT => 'Số tiền không hợp lệ', self::INVALID_CURRENCY => 'Đơn vị tiền tệ không được hỗ trợ', self::INVALID_PAYMENT_METHOD => 'Phương thức thanh toán không hợp lệ', self::DUPLICATE_ORDER_ID => 'Mã đơn hàng đã tồn tại', // Business Logic self::PROJECT_NOT_FOUND => 'Không tìm thấy dự án', self::PROJECT_INACTIVE => 'Dự án đã bị vô hiệu hóa', self::PROJECT_HAS_TENANTS => 'Không thể xóa dự án vì còn tồn tại tenant', self::TENANT_NOT_FOUND => 'Không tìm thấy tenant', self::TENANT_INACTIVE => 'Tenant đã bị vô hiệu hóa', self::TENANT_LIMIT_EXCEEDED => 'Vượt quá hạn mức của tenant', self::TRANSACTION_NOT_FOUND => 'Không tìm thấy giao dịch', self::TRANSACTION_EXPIRED => 'Giao dịch đã hết hạn', self::TRANSACTION_ALREADY_PROCESSED => 'Giao dịch đã được xử lý', self::TRANSACTION_CANNOT_BE_REFUNDED => 'Giao dịch không thể hoàn tiền', // Payment Provider self::PROVIDER_NOT_CONFIGURED => 'Cổng thanh toán chưa được cấu hình', self::PROVIDER_INACTIVE => 'Cổng thanh toán đã bị vô hiệu hóa', self::PROVIDER_CONNECTION_ERROR => 'Không thể kết nối đến cổng thanh toán', self::PROVIDER_INVALID_RESPONSE => 'Phản hồi từ cổng thanh toán không hợp lệ', self::PROVIDER_AUTHENTICATION_FAILED => 'Xác thực với cổng thanh toán thất bại', // Webhook self::WEBHOOK_INVALID_SIGNATURE => 'Chữ ký webhook không hợp lệ', self::WEBHOOK_INVALID_IP => 'IP address không được phép', self::WEBHOOK_PROCESSING_ERROR => 'Lỗi xử lý webhook', self::WEBHOOK_DELIVERY_FAILED => 'Gửi webhook thất bại', // Reconciliation self::RECONCILIATION_IN_PROGRESS => 'Đối soát đang được thực hiện', self::RECONCILIATION_FAILED => 'Đối soát thất bại', self::RECONCILIATION_DATA_MISMATCH => 'Dữ liệu đối soát không khớp', default => 'Đã xảy ra lỗi không xác định' }; } public static function getHttpStatusCode(string $code): int { return match($code) { // 400 Bad Request self::VALIDATION_ERROR, self::INVALID_AMOUNT, self::INVALID_CURRENCY, self::INVALID_PAYMENT_METHOD, self::DUPLICATE_ORDER_ID, self::TRANSACTION_EXPIRED, self::TRANSACTION_ALREADY_PROCESSED => 400, // 401 Unauthorized self::UNAUTHORIZED, self::INVALID_API_KEY => 401, // 403 Forbidden self::FORBIDDEN, self::PROJECT_INACTIVE, self::TENANT_INACTIVE, self::WEBHOOK_INVALID_IP => 403, // 404 Not Found self::PROJECT_NOT_FOUND, self::TENANT_NOT_FOUND, self::TRANSACTION_NOT_FOUND => 404, // 409 Conflict self::PROJECT_HAS_TENANTS, self::RECONCILIATION_IN_PROGRESS => 409, // 422 Unprocessable Entity self::PROVIDER_NOT_CONFIGURED, self::TRANSACTION_CANNOT_BE_REFUNDED => 422, // 429 Too Many Requests self::RATE_LIMIT_EXCEEDED, self::TENANT_LIMIT_EXCEEDED => 429, // 502 Bad Gateway self::PROVIDER_CONNECTION_ERROR, self::PROVIDER_INVALID_RESPONSE => 502, // 503 Service Unavailable self::PROVIDER_INACTIVE => 503, // 500 Internal Server Error (default) default => 500 }; } } ``` --- ## 11. PERFORMANCE VÀ SCALING ### 11.1. Database Optimization #### 11.1.1. Query Optimization Strategies ```php

with($with); // Apply filters with proper indexing if (!empty($filters['tenant_id'])) { $query->where('tenant_id', $filters['tenant_id']); } if (!empty($filters['status'])) { $query->where('status', $filters['status']); } if (!empty($filters['date_from'])) { $query->where('created_at', '>=', $filters['date_from']); } if (!empty($filters['date_to'])) { $query->where('created_at', '<=', $filters['date_to']); } if (!empty($filters['amount_from'])) { $query->where('amount', '>=', $filters['amount_from']); } if (!empty($filters['amount_to'])) { $query->where('amount', '<=', $filters['amount_to']); } if (!empty($filters['provider_code'])) { $query->whereHas('paymentProvider', function($q) use ($filters) { $q->where('code', $filters['provider_code']); }); } if (!empty($filters['search'])) { $query->where(function($q) use ($filters) { $q->where('order_id', 'like', "%{$filters['search']}%") ->orWhere('provider_transaction_id', 'like', "%{$filters['search']}%") ->orWhereJsonContains('customer_info->email', $filters['search']); }); } // Use appropriate indexes for sorting $sortField = $filters['sort'] ?? 'created_at'; $sortDirection = $filters['direction'] ?? 'desc'; $query->orderBy($sortField, $sortDirection); // Add secondary sort for consistency if ($sortField !== 'id') { $query->orderBy('id', 'desc'); } return $query->paginate($perPage, ['*'], 'page', $page); } public function getStatistics(array $filters = []): array { $query = Transaction::query(); // Apply same filters as pagination $this->applyFilters($query, $filters); // Use database aggregation for better performance $stats = $query->selectRaw(' COUNT(*) as total_transactions, COUNT(CASE WHEN status = "completed" THEN 1 END) as completed_count, COUNT(CASE WHEN status = "failed" THEN 1 END) as failed_count, COUNT(CASE WHEN status = "pending" THEN 1 END) as pending_count, SUM(CASE WHEN status = "completed" THEN amount ELSE 0 END) as total_amount, SUM(CASE WHEN status = "completed" THEN transaction_fee ELSE 0 END) as total_fees, AVG(CASE WHEN status = "completed" THEN amount END) as avg_amount ')->first(); return [ 'total_transactions' => (int) $stats->total_transactions, 'completed_count' => (int) $stats->completed_count, 'failed_count' => (int) $stats->failed_count, 'pending_count' => (int) $stats->pending_count, 'total_amount' => (float) $stats->total_amount, 'total_fees' => (float) $stats->total_fees, 'average_amount' => (float) $stats->avg_amount, 'success_rate' => $stats->total_transactions > 0 ? round(($stats->completed_count / $stats->total_transactions) * 100, 2) : 0 ]; } } ``` ### 11.2. Caching Strategy #### 11.2.1. Multi-layer Caching Implementation ```php

['ttl' => self::LONG_TTL, 'tags' => ['projects']], 'tenants' => ['ttl' => self::LONG_TTL, 'tags' => ['tenants']], 'payment_providers' => ['ttl' => self::LONG_TTL, 'tags' => ['providers']], 'tenant_providers' => ['ttl' => self::DEFAULT_TTL, 'tags' => ['tenants', 'providers']], 'transactions' => ['ttl' => self::SHORT_TTL, 'tags' => ['transactions']], 'system_config' => ['ttl' => self::LONG_TTL, 'tags' => ['config']], 'statistics' => ['ttl' => self::SHORT_TTL, 'tags' => ['stats']] ]; public function get(string $key, $default = null) { return Cache::get($key, $default); } public function put(string $key, $value, string $type = 'default', int $ttl = null): bool { $config = self::CACHE_CONFIG[$type] ?? ['ttl' => self::DEFAULT_TTL, 'tags' => []]; $ttl = $ttl ?? $config['ttl']; if (!empty($config['tags'])) { return Cache::tags($config['tags'])->put($key, $value, $ttl); } return Cache::put($key, $value, $ttl); } public function remember(string $key, callable $callback, string $type = 'default', int $ttl = null) { $config = self::CACHE_CONFIG[$type] ?? ['ttl' => self::DEFAULT_TTL, 'tags' => []]; $ttl = $ttl ?? $config['ttl']; if (!empty($config['tags'])) { return Cache::tags($config['tags'])->remember($key, $ttl, $callback); } return Cache::remember($key, $ttl, $callback); } public function forget(string $key): bool { return Cache::forget($key); } public function flush(array $tags = []): bool { if (empty($tags)) { return Cache::flush(); } return Cache::tags($tags)->flush(); } // High-performance methods using Redis directly public function getFromRedis(string $key, $default = null) { $value = Redis::get($key); if ($value === null) { return $default; } return json_decode($value, true); } public function putToRedis(string $key, $value, int $ttl = self::DEFAULT_TTL): bool { return Redis::setex($key, $ttl, json_encode($value)); } // Atomic operations for counters public function increment(string $key, int $value = 1): int { return Redis::incrby($key, $value); } public function decrement(string $key, int $value = 1): int { return Redis::decrby($key, $value); } // Lock mechanism for critical sections public function lock(string $key, int $ttl = 30): bool { return Redis::set("lock:{$key}", 1, 'EX', $ttl, 'NX'); } public function unlock(string $key): bool { return Redis::del("lock:{$key}") > 0; } // Bulk operations for better performance public function multiGet(array $keys): array { $values = Redis::mget($keys); $result = []; foreach ($keys as $index => $key) { $result[$key] = $values[$index] ? json_decode($values[$index], true) : null; } return $result; } public function multiSet(array $data, int $ttl = self::DEFAULT_TTL): bool { $pipeline = Redis::pipeline(); foreach ($data as $key => $value) { $pipeline->setex($key, $ttl, json_encode($value)); } $results = $pipeline->execute(); return !in_array(false, $results, true); } } ``` ### 11.3. Auto-scaling Configuration #### 11.3.1. Kubernetes Deployment Configuration ```yaml # kubernetes/deployment.yaml apiVersion: apps/v1 kind: Deployment metadata: name: smartpost-payment-service labels: app: smartpost-payment-service spec: replicas: 3 selector: matchLabels: app: smartpost-payment-service template: metadata: labels: app: smartpost-payment-service spec: containers: - name: app image: smartpost/payment-service:latest ports: - containerPort: 80 env: - name: APP_ENV value: "production" - name: DB_HOST valueFrom: secretKeyRef: name: database-secret key: host resources: requests: memory: "256Mi" cpu: "250m" limits: memory: "512Mi" cpu: "500m" livenessProbe: httpGet: path: /api/v1/status port: 80 initialDelaySeconds: 30 periodSeconds: 10 readinessProbe: httpGet: path: /api/v1/status port: 80 initialDelaySeconds: 5 periodSeconds: 5 --- apiVersion: v1 kind: Service metadata: name: smartpost-payment-service spec: selector: app: smartpost-payment-service ports: - port: 80 targetPort: 80 type: LoadBalancer --- apiVersion: autoscaling/v2 kind: HorizontalPodAutoscaler metadata: name: smartpost-payment-hpa spec: scaleTargetRef: apiVersion: apps/v1 kind: Deployment name: smartpost-payment-service minReplicas: 3 maxReplicas: 20 metrics: - type: Resource resource: name: cpu target: type: Utilization averageUtilization: 70 - type: Resource resource: name: memory target: type: Utilization averageUtilization: 80 behavior: scaleDown: stabilizationWindowSeconds: 300 policies: - type: Percent value: 50 periodSeconds: 60 scaleUp: stabilizationWindowSeconds: 60 policies: - type: Percent value: 100 periodSeconds: 60 - type: Pods value: 5 periodSeconds: 60 selectPolicy: Max ``` --- ## 12. SECURITY VÀ COMPLIANCE ### 12.1. Data Encryption #### 12.1.1. Sensitive Data Encryption Service ```php

encrypter = new Encrypter( config('app.encryption_key'), config('app.cipher') ); $this->keyRotationEnabled = config('app.key_rotation_enabled', false); } public function encrypt(string $value, string $context = ''): string { $data = [ 'value' => $value, 'context' => $context, 'timestamp' => now()->timestamp, 'version' => config('app.encryption_version', 1) ]; return $this->encrypter->encrypt($data); } public function decrypt(string $encryptedValue): string { $data = $this->encrypter->decrypt($encryptedValue); // Handle key rotation if ($this->keyRotationEnabled && $data['version'] < config('app.encryption_version')) { // Re-encrypt with new key $newEncrypted = $this->encrypt($data['value'], $data['context']); // Update database record (implement based on context) $this->updateEncryptedValue($encryptedValue, $newEncrypted, $data['context']); } return $data['value']; } public function hash(string $value, string $salt = ''): string { return hash_hmac('sha256', $value, config('app.key') . $salt); } public function generateSecureToken(int $length = 32): string { return bin2hex(random_bytes($length)); } private function updateEncryptedValue(string $old, string $new, string $context): void { // Implement based on context - update database records // This would typically involve identifying the record and updating it \Log::info('Key rotation: Re-encrypted value', [ 'context' => $context, 'old_hash' => substr(md5($old), 0, 8), 'new_hash' => substr(md5($new), 0, 8) ]); } } ``` ### 12.2. Compliance Framework #### 12.2.1. GDPR Compliance Service ```php

auditService = $auditService; $this->encryptionService = $encryptionService; } public function anonymizePersonalData(string $tenantId, string $dataSubject): array { $results = []; // Anonymize transaction data $results['transactions'] = $this->anonymizeTransactionData($tenantId, $dataSubject); // Anonymize audit logs $results['audit_logs'] = $this->anonymizeAuditLogs($tenantId, $dataSubject); // Anonymize webhook logs $results['webhook_logs'] = $this->anonymizeWebhookLogs($tenantId, $dataSubject); // Log the anonymization action $this->auditService->logAction([ 'tenant_id' => $tenantId, 'entity_type' => 'personal_data', 'entity_id' => $dataSubject, 'action' => 'anonymized', 'additional_data' => [ 'anonymization_results' => $results, 'requested_at' => now()->toISOString(), 'retention_policy' => 'gdpr_article_17' ] ]); return $results; } public function exportPersonalData(string $tenantId, string $dataSubject): array { $exportData = [ 'data_subject' => $dataSubject, 'tenant_id' => $tenantId, 'export_date' => now()->toISOString(), 'data_categories' => [] ]; // Export transaction data $exportData['data_categories']['transactions'] = $this->exportTransactionData($tenantId, $dataSubject); // Export customer information $exportData['data_categories']['customer_info'] = $this->exportCustomerInfo($tenantId, $dataSubject); // Export audit trail $exportData['data_categories']['audit_trail'] = $this->exportAuditTrail($tenantId, $dataSubject); // Log the export action $this->auditService->logAction([ 'tenant_id' => $tenantId, 'entity_type' => 'personal_data', 'entity_id' => $dataSubject, 'action' => 'exported', 'additional_data' => [ 'export_size' => strlen(json_encode($exportData)), 'categories_count' => count($exportData['data_categories']), 'gdpr_article' => 'article_15' ] ]); return $exportData; } public function deletePersonalData(string $tenantId, string $dataSubject, string $legalBasis): array { $deletionResults = []; // Delete from transactions (customer_info) $deletionResults['transactions'] = \DB::table('transactions') ->where('tenant_id', $tenantId) ->whereJsonContains('customer_info->email', $dataSubject) ->update([ 'customer_info' => json_encode(['deleted' => true, 'deleted_at' => now()]), 'updated_at' => now() ]); // Delete from audit logs $deletionResults['audit_logs'] = \DB::table('audit_logs') ->where('tenant_id', $tenantId) ->where('additional_data->customer_email', $dataSubject) ->delete(); // Log the deletion $this->auditService->logAction([ 'tenant_id' => $tenantId, 'entity_type' => 'personal_data', 'entity_id' => $dataSubject, 'action' => 'deleted', 'additional_data' => [ 'legal_basis' => $legalBasis, 'deletion_results' => $deletionResults, 'gdpr_article' => 'article_17' ] ]); return $deletionResults; } public function applyDataRetentionPolicy(): array { $retentionDays = config('app.data_retention_days', 2555); // 7 years default $cutoffDate = now()->subDays($retentionDays); $results = [ 'cutoff_date' => $cutoffDate->toISOString(), 'deleted_records' => [] ]; // Archive old transactions $results['deleted_records']['transactions'] = $this->archiveOldTransactions($cutoffDate); // Clean up old audit logs $results['deleted_records']['audit_logs'] = $this->cleanupOldAuditLogs($cutoffDate); // Clean up old webhook logs $results['deleted_records']['webhook_logs'] = $this->cleanupOldWebhookLogs($cutoffDate); return $results; } private function anonymizeTransactionData(string $tenantId, string $dataSubject): int { return \DB::table('transactions') ->where('tenant_id', $tenantId) ->whereJsonContains('customer_info->email', $dataSubject) ->update([ 'customer_info' => json_encode([ 'name' => 'ANONYMIZED', 'email' => 'anonymized@example.com', 'phone' => 'ANONYMIZED', 'address' => 'ANONYMIZED', 'anonymized_at' => now()->toISOString() ]), 'updated_at' => now() ]); } private function exportTransactionData(string $tenantId, string $dataSubject): array { return \DB::table('transactions') ->where('tenant_id', $tenantId) ->whereJsonContains('customer_info->email', $dataSubject) ->select([ 'id', 'order_id', 'amount', 'currency', 'status', 'customer_info', 'created_at', 'paid_at' ]) ->get() ->toArray(); } } ``` ---