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**
  • Framework: Laravel 11.x
  • - **Architecture**
  • Architecture: Domain Driven Design (DDD) + CQRS
  • - **Database**
  • Database: MySQL 8.0 (Master-Slave for read scaling)
  • - **Cache**
  • Cache: Redis Cluster
  • - **Queue**
  • Queue: Redis + Laravel Horizon
  • - **Search**
  • Search: Elasticsearch 8.x
  • - **Monitoring**
  • Monitoring: ELK Stack + Grafana + Prometheus
  • - **Container**
  • Container: Docker + Kubernetes
  • - **Authentication**
  • Authentication: Internal service (Network security)
  • - **
  • API Documentation**Documentation: OpenAPI 3.0 + Swagger UI
  • - **Testing**
  • Testing: PHPUnit + Pest + Feature Tests
  • ###

1.3. Security Model

-
    **
  • Network Security**Security: Service được bảo vệ bởi VPN, Firewall và Service Mesh
  • - **
  • Request Validation**Validation: Validate project_code và app_code từ internal registry
  • - **
  • Webhook Security**Security: Multi-layer signature validation
  • - **
  • Rate Limiting**Limiting: Intelligent rate limiting với Redis
  • - **
  • Data Encryption**Encryption: AES-256 cho sensitive data
  • - **
  • Audit Trail**Trail: Comprehensive audit logging
  • - **
  • GDPR Compliance**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**

POST `/api/v1/{project_code}/{app_code}/transactions`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**

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**

POST `/api/v1/{project_code}/{app_code}/transactions/{transaction_id}/refund`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**

POST `/api/v1/webhooks/vnpay`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

namespace

App\Http\Middleware\Request; use Closure; use Illuminate\Http\Request; use App\Domain\Project\Repositories\ProjectRepositoryInterface; use App\Domain\Tenant\Repositories\TenantRepositoryInterface; use App\Exceptions\ProjectNotFoundException; use App\Exceptions\TenantNotFoundException; class ResolveProjectAndTenantMiddleware { private $projectRepository; private $tenantRepository; public function __construct( ProjectRepositoryInterface $projectRepository, TenantRepositoryInterface $tenantRepository ) { $this->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

namespace

App\Http\Middleware\Logging; use Closure; use Illuminate\Http\Request; use App\Domain\Shared\Services\AuditLogService; use Illuminate\Support\Str; class AuditLogMiddleware { private $auditService; public function __construct(AuditLogService $auditService) { $this->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

namespace

App\Http\Middleware\Request; use Closure; use Illuminate\Http\Request; use App\Infrastructure\Cache\CacheManager; use App\Domain\Shared\Services\IdempotencyService; class IdempotencyMiddleware { private $idempotencyService; public function __construct(IdempotencyService $idempotencyService) { $this->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

namespace

App\Http\Middleware\Security; use Closure; use Illuminate\Http\Request; use App\Domain\Webhook\Services\WebhookSecurityService; class ValidateWebhookSignatureMiddleware { private $webhookSecurity; public function __construct(WebhookSecurityService $webhookSecurity) { $this->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

namespace

App\Http\Controllers\API\V1\Webhook; use Illuminate\Http\Request; use App\Http\Controllers\API\BaseApiController; use App\Domain\Webhook\Services\WebhookService; use App\Infrastructure\Queue\Jobs\ProcessWebhookJob; class BaseWebhookController extends BaseApiController { private $webhookService; public function __construct(WebhookService $webhookService) { $this->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

namespace

App\Infrastructure\Queue\Jobs; use Illuminate\Bus\Queueable; use Illuminate\Contracts\Queue\ShouldQueue; use Illuminate\Foundation\Bus\Dispatchable; use Illuminate\Queue\InteractsWithQueue; use Illuminate\Queue\SerializesModels; use App\Domain\Webhook\Services\WebhookService; use App\Domain\Payment\Services\TransactionService; use App\Infrastructure\Queue\Jobs\SendCallbackJob; class ProcessWebhookJob implements ShouldQueue { use Dispatchable, InteractsWithQueue, Queueable, SerializesModels; public $tries = 3; public $backoff = [10, 30, 60]; // seconds private $webhookLogId; private $provider; private $payload; public function __construct(string $webhookLogId, string $provider, array $payload) { $this->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

namespace

App\Domain\Payment\Services; use App\Domain\Payment\Entities\Transaction; use App\Domain\Payment\Events\FraudDetected; class FraudDetectionService { private $rules = []; public function __construct() { $this->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

namespace

App\Infrastructure\External\Common; use App\Infrastructure\Cache\CacheManager; class CircuitBreaker { private $cache; private $failureThreshold; private $recoveryTimeout; private $monitoringPeriod; public function __construct( CacheManager $cache, int $failureThreshold = 5, int $recoveryTimeout = 60, int $monitoringPeriod = 300 ) { $this->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

namespace

App\Infrastructure\External\Common; class RetryHandler { private $maxAttempts; private $baseDelay; private $maxDelay; private $multiplier; public function __construct( int $maxAttempts = 3, int $baseDelay = 1000, // milliseconds int $maxDelay = 30000, // milliseconds float $multiplier = 2.0 ) { $this->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

namespace

App\Domain\Shared\Services; use App\Infrastructure\Persistence\Models\AuditLog; use Illuminate\Support\Facades\Auth; class AuditLogService { public function logAction(array $data): AuditLog { return AuditLog::create([ 'id' => \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

namespace

App\Domain\System\Services; use Illuminate\Support\Facades\DB; use Illuminate\Support\Facades\Redis; use App\Infrastructure\External\PaymentProviders\VNPay\VNPayClient; class SystemHealthService { public function getSystemHealth(): array { return [ 'overall_status' => $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

namespace

App\Infrastructure\Queue\Jobs; use Illuminate\Bus\Queueable; use Illuminate\Contracts\Queue\ShouldQueue; use Illuminate\Foundation\Bus\Dispatchable; use Illuminate\Queue\InteractsWithQueue; use Illuminate\Queue\SerializesModels; use Illuminate\Support\Facades\Http; use App\Domain\Payment\Repositories\TransactionRepositoryInterface; use App\Domain\Shared\Services\AuditLogService; use App\Infrastructure\External\Common\RetryHandler; class SendCallbackJob implements ShouldQueue { use Dispatchable, InteractsWithQueue, Queueable, SerializesModels; public $tries = 5; public $backoff = [10, 30, 60, 300, 900]; // seconds: 10s, 30s, 1m, 5m, 15m public $timeout = 30; private $transactionId; private $callbackUrl; private $callbackData; private $attempt; public function __construct( string $transactionId, string $callbackUrl, array $callbackData, int $attempt = 1 ) { $this->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

namespace

App\Infrastructure\Queue\Jobs; use Illuminate\Bus\Queueable; use Illuminate\Contracts\Queue\ShouldQueue; use Illuminate\Foundation\Bus\Dispatchable; use Illuminate\Queue\InteractsWithQueue; use Illuminate\Queue\SerializesModels; use App\Domain\Reconciliation\Services\ReconciliationService; use Carbon\Carbon; class ProcessReconciliationJob implements ShouldQueue { use Dispatchable, InteractsWithQueue, Queueable, SerializesModels; public $tries = 3; public $timeout = 600; // 10 minutes private $tenantId; private $providerId; private $reconciliationDate; public function __construct(string $tenantId, string $providerId, Carbon $reconciliationDate) { $this->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

namespace

App\Infrastructure\Queue\Jobs; use Illuminate\Bus\Queueable; use Illuminate\Contracts\Queue\ShouldQueue; use Illuminate\Foundation\Bus\Dispatchable; use Illuminate\Queue\InteractsWithQueue; use Illuminate\Queue\SerializesModels; use App\Domain\Shared\Services\DataRetentionService; class CleanupExpiredDataJob implements ShouldQueue { use Dispatchable, InteractsWithQueue, Queueable, SerializesModels; public $tries = 1; public $timeout = 3600; // 1 hour public function __construct() { $this->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

namespace

App\Exceptions; abstract class BaseException extends \Exception { protected $errorCode; protected $errorData; protected $httpStatusCode; public function __construct( string $message = '', string $errorCode = '', array $errorData = [], int $httpStatusCode = 400, \Exception $previous = null ) { parent::__construct($message, 0, $previous); $this->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

namespace

App\Exceptions; use Illuminate\Foundation\Exceptions\Handler as ExceptionHandler; use Illuminate\Http\JsonResponse; use Illuminate\Http\Request; use Illuminate\Http\Response; use Illuminate\Validation\ValidationException as LaravelValidationException; use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; use Symfony\Component\HttpKernel\Exception\MethodNotAllowedHttpException; use Throwable; class Handler extends ExceptionHandler { protected $dontFlash = [ 'current_password', 'password', 'password_confirmation', 'secret_key', 'api_key', 'webhook_secret' ]; protected $dontReport = [ ValidationException::class, LaravelValidationException::class, NotFoundHttpException::class, MethodNotAllowedHttpException::class ]; public function register() { $this->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

namespace

App\Constants; class ErrorCodes { // System Errors (1000-1999) const SYSTEM_ERROR = 'SYSTEM_ERROR'; const DATABASE_ERROR = 'DATABASE_ERROR'; const NETWORK_ERROR = 'NETWORK_ERROR'; const CACHE_ERROR = 'CACHE_ERROR'; const QUEUE_ERROR = 'QUEUE_ERROR'; // Authentication & Authorization Errors (2000-2999) const UNAUTHORIZED = 'UNAUTHORIZED'; const FORBIDDEN = 'FORBIDDEN'; const INVALID_API_KEY = 'INVALID_API_KEY'; const RATE_LIMIT_EXCEEDED = 'RATE_LIMIT_EXCEEDED'; // Validation Errors (3000-3999) const VALIDATION_ERROR = 'VALIDATION_ERROR'; const INVALID_AMOUNT = 'INVALID_AMOUNT'; const INVALID_CURRENCY = 'INVALID_CURRENCY'; const INVALID_PAYMENT_METHOD = 'INVALID_PAYMENT_METHOD'; const DUPLICATE_ORDER_ID = 'DUPLICATE_ORDER_ID'; // Business Logic Errors (4000-4999) const PROJECT_NOT_FOUND = 'PROJECT_NOT_FOUND'; const PROJECT_INACTIVE = 'PROJECT_INACTIVE'; const PROJECT_HAS_TENANTS = 'PROJECT_HAS_TENANTS'; const TENANT_NOT_FOUND = 'TENANT_NOT_FOUND'; const TENANT_INACTIVE = 'TENANT_INACTIVE'; const TENANT_LIMIT_EXCEEDED = 'TENANT_LIMIT_EXCEEDED'; const TRANSACTION_NOT_FOUND = 'TRANSACTION_NOT_FOUND'; const TRANSACTION_EXPIRED = 'TRANSACTION_EXPIRED'; const TRANSACTION_ALREADY_PROCESSED = 'TRANSACTION_ALREADY_PROCESSED'; const TRANSACTION_CANNOT_BE_REFUNDED = 'TRANSACTION_CANNOT_BE_REFUNDED'; // Payment Provider Errors (5000-5999) const PROVIDER_NOT_CONFIGURED = 'PROVIDER_NOT_CONFIGURED'; const PROVIDER_INACTIVE = 'PROVIDER_INACTIVE'; const PROVIDER_CONNECTION_ERROR = 'PROVIDER_CONNECTION_ERROR'; const PROVIDER_INVALID_RESPONSE = 'PROVIDER_INVALID_RESPONSE'; const PROVIDER_AUTHENTICATION_FAILED = 'PROVIDER_AUTHENTICATION_FAILED'; // Webhook Errors (6000-6999) const WEBHOOK_INVALID_SIGNATURE = 'WEBHOOK_INVALID_SIGNATURE'; const WEBHOOK_INVALID_IP = 'WEBHOOK_INVALID_IP'; const WEBHOOK_PROCESSING_ERROR = 'WEBHOOK_PROCESSING_ERROR'; const WEBHOOK_DELIVERY_FAILED = 'WEBHOOK_DELIVERY_FAILED'; // Reconciliation Errors (7000-7999) const RECONCILIATION_IN_PROGRESS = 'RECONCILIATION_IN_PROGRESS'; const RECONCILIATION_FAILED = 'RECONCILIATION_FAILED'; const RECONCILIATION_DATA_MISMATCH = 'RECONCILIATION_DATA_MISMATCH'; public static function getMessage(string $code): string { return match($code) { // System Errors self::SYSTEM_ERROR => 'Đã 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

namespace

App\Infrastructure\Persistence\Repositories\Eloquent; use App\Domain\Payment\Repositories\TransactionRepositoryInterface; use App\Infrastructure\Persistence\Models\Transaction; use Illuminate\Pagination\LengthAwarePaginator; class EloquentTransactionRepository implements TransactionRepositoryInterface { public function findWithPagination( array $filters = [], int $perPage = 20, int $page = 1, array $with = [] ): LengthAwarePaginator { $query = Transaction::query(); // Always eager load commonly used relationships $defaultWith = ['tenant', 'paymentProvider', 'events']; $with = array_merge($defaultWith, $with); $query->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

namespace

App\Infrastructure\Cache; use Illuminate\Support\Facades\Cache; use Illuminate\Support\Facades\Redis; class CacheManager { private const DEFAULT_TTL = 3600; // 1 hour private const SHORT_TTL = 300; // 5 minutes private const LONG_TTL = 86400; // 24 hours // Cache configuration for different entities private const CACHE_CONFIG = [ 'projects' => ['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

namespace

App\Domain\Shared\Services; use Illuminate\Encryption\Encrypter; class EncryptionService { private $encrypter; private $keyRotationEnabled; public function __construct() { $this->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

namespace

App\Domain\Shared\Services; use App\Infrastructure\Persistence\Models\AuditLog; use Carbon\Carbon; class ComplianceService { private $auditService; private $encryptionService; public function __construct( AuditLogService $auditService, EncryptionService $encryptionService ) { $this->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(); } } ``` ---