Initial commit - WASL Digital Wallet

This commit is contained in:
Hamza-Ayed
2026-06-20 21:55:06 +03:00
commit 7306c47368
61 changed files with 4157 additions and 0 deletions

View File

@@ -0,0 +1,40 @@
<?php
return [
'enabled' => env('ACTIVITY_LOGGER_ENABLED', true),
/*
|-------------------------------------------------------------------
| Log deleted records
|-------------------------------------------------------------------
*/
'delete_records_on_delete' => false,
'default_log_name' => 'default',
/*
|-------------------------------------------------------------------
| Default driver
|-------------------------------------------------------------------
*/
'driver' => \Spatie\Activitylog\ActivitylogServiceProvider::class,
/*
|-------------------------------------------------------------------
| Show activity log model
|-------------------------------------------------------------------
*/
'activity_model' => \App\Models\Activitylog\Activity::class,
/*
|-------------------------------------------------------------------
| The database connection used for activity logs.
|-------------------------------------------------------------------
*/
'database_connection' => env('ACTIVITYLOG_DB_CONNECTION', 'pgsql'),
];

97
Backend/config/app.php Normal file
View File

@@ -0,0 +1,97 @@
<?php
return [
/*
|--------------------------------------------------------------------------
| Application Name
|--------------------------------------------------------------------------
*/
'name' => env('APP_NAME', 'WASL'),
'short_name' => env('APP_SHORT_NAME', 'وَصْل'),
/*
|--------------------------------------------------------------------------
| Application Environment
|--------------------------------------------------------------------------
*/
'env' => env('APP_ENV', 'production'),
/*
|--------------------------------------------------------------------------
| Application Debug Mode
|--------------------------------------------------------------------------
| SECURITY: NEVER enable debug mode in production for a financial system.
*/
'debug' => (bool) env('APP_DEBUG', false),
/*
|--------------------------------------------------------------------------
| Application URL
|--------------------------------------------------------------------------
*/
'url' => env('APP_URL', 'http://localhost'),
'asset_url' => env('ASSET_URL'),
/*
|--------------------------------------------------------------------------
| Application Timezone
|--------------------------------------------------------------------------
| Syria uses Asia/Damascus (UTC+03:00, no DST since 2022)
*/
'timezone' => 'Asia/Damascus',
/*
|--------------------------------------------------------------------------
| Application Locale Configuration
|--------------------------------------------------------------------------
*/
'locale' => 'ar',
'fallback_locale' => 'en',
'faker_locale' => 'ar_SY',
/*
|--------------------------------------------------------------------------
| Encryption Key
|--------------------------------------------------------------------------
*/
'key' => env('APP_KEY'),
'cipher' => env('APP_CIPHER', 'aes-256-cbc'),
/*
|--------------------------------------------------------------------------
| Maintenance Mode Driver
|--------------------------------------------------------------------------
*/
'maintenance' => [
'driver' => env('APP_MAINTENANCE_DRIVER', 'file'),
'store' => env('APP_MAINTENANCE_STORE', 'db'),
],
/*
|--------------------------------------------------------------------------
| WASL-specific config reference
|--------------------------------------------------------------------------
*/
'wasl' => [
'country_code' => 'SY',
'default_currency' => 'SYP',
'minor_unit_decimals' => [
'SYP' => 2, // 1 SYP = 100 piasters → minor unit
'USD' => 2,
'EUR' => 2,
'AED' => 2,
'USDT' => 8, // crypto precision
'BTC' => 8,
],
],
];

40
Backend/config/auth.php Normal file
View File

@@ -0,0 +1,40 @@
<?php
return [
'defaults' => [
'guard' => 'api',
],
'guards' => [
'api' => [
'driver' => 'jwt',
'provider' => 'users',
],
// Filament admin panel
'web' => [
'driver' => 'session',
'provider' => 'users',
],
],
'providers' => [
'users' => [
'driver' => 'eloquent',
'model' => env('AUTH_MODEL', App\Models\User::class),
],
],
'passwords' => [
'users' => [
'provider' => 'users',
'table' => 'password_reset_tokens',
'expire' => 60,
'throttle' => 60,
],
],
'password_timeout' => 10800,
];

123
Backend/config/database.php Normal file
View File

@@ -0,0 +1,123 @@
<?php
use Illuminate\Support\ServiceProvider;
return [
/*
|--------------------------------------------------------------------------
| Default Database Connection Name
|--------------------------------------------------------------------------
*/
'default' => env('DB_CONNECTION', 'pgsql'),
/*
|--------------------------------------------------------------------------
| Database Connections
|--------------------------------------------------------------------------
*/
'connections' => [
'pgsql' => [
'driver' => 'pgsql',
'url' => env('DB_URL'),
'host' => env('DB_HOST', '127.0.0.1'),
'port' => env('DB_PORT', '5432'),
'database' => env('DB_DATABASE', 'wasl'),
'username' => env('DB_USERNAME', 'wasl'),
'password' => env('DB_PASSWORD', ''),
'charset' => 'utf8',
'prefix' => '',
'prefix_indexes' => true,
'search_path' => 'public',
'sslmode' => env('DB_SSL_MODE', 'prefer'),
'sslcert' => env('DB_SSL_CERT'),
'sslkey' => env('DB_SSL_KEY'),
'sslrootcert' => env('DB_SSL_ROOT_CERT'),
'schema' => 'public',
'engine' => null,
],
'testing' => [
'driver' => 'pgsql',
'host' => env('DB_TEST_HOST', '127.0.0.1'),
'port' => env('DB_TEST_PORT', '5432'),
'database' => env('DB_TEST_DATABASE', 'wasl_testing'),
'username' => env('DB_TEST_USERNAME', 'wasl'),
'password' => env('DB_TEST_PASSWORD', ''),
'charset' => 'utf8',
'search_path' => 'public',
],
],
/*
|--------------------------------------------------------------------------
| Migration Repository Table
|--------------------------------------------------------------------------
*/
'migrations' => [
'table' => 'migrations',
'update_date_on_publish' => true,
],
/*
|--------------------------------------------------------------------------
| Redis Databases / Connection
| Separate logical DBs isolate concerns: cache, queue, idempotency,
| throttling, sessions — so a flush of one never impacts the others.
|--------------------------------------------------------------------------
*/
'redis' => [
'client' => env('REDIS_CLIENT', 'predis'),
'options' => [
'cluster' => env('REDIS_CLUSTER', 'redis'),
'prefix' => env('REDIS_PREFIX', 'wasl_'),
'persistent' => env('REDIS_PERSISTENT', false),
],
'default' => [
'url' => env('REDIS_URL'),
'host' => env('REDIS_HOST', '127.0.0.1'),
'username' => env('REDIS_USERNAME'),
'password' => env('REDIS_PASSWORD'),
'port' => env('REDIS_PORT', '6379'),
'database' => env('REDIS_DB', '0'),
],
'cache' => [
'url' => env('REDIS_URL'),
'host' => env('REDIS_HOST', '127.0.0.1'),
'username' => env('REDIS_USERNAME'),
'password' => env('REDIS_PASSWORD'),
'port' => env('REDIS_PORT', '6379'),
'database' => env('REDIS_CACHE_DB', '1'),
],
// Separate DB for idempotency keys (short TTL)
'idempotency' => [
'url' => env('REDIS_URL'),
'host' => env('REDIS_HOST', '127.0.0.1'),
'username' => env('REDIS_USERNAME'),
'password' => env('REDIS_PASSWORD'),
'port' => env('REDIS_PORT', '6379'),
'database' => env('REDIS_IDEMPOTENCY_DB', '2'),
],
// Separate DB for rate limiting / throttling
'throttle' => [
'url' => env('REDIS_URL'),
'host' => env('REDIS_HOST', '127.0.0.1'),
'username' => env('REDIS_USERNAME'),
'password' => env('REDIS_PASSWORD'),
'port' => env('REDIS_PORT', '6379'),
'database' => env('REDIS_THROTTLE_DB', '3'),
],
],
];

View File

@@ -0,0 +1,62 @@
<?php
return [
'default' => env('FILESYSTEM_DISK', 'local'),
'disks' => [
'local' => [
'driver' => 'local',
'root' => storage_path('app/private'),
'serve' => true,
'throw' => true,
],
'public' => [
'driver' => 'local',
'root' => storage_path('app/public'),
'url' => env('APP_URL').'/storage',
'visibility' => 'public',
'throw' => true,
],
// MinIO — used for KYC documents (encrypted before upload)
's3' => [
'driver' => 's3',
'key' => env('AWS_ACCESS_KEY_ID'),
'secret' => env('AWS_SECRET_ACCESS_KEY'),
'region' => env('AWS_DEFAULT_REGION', 'us-east-1'),
'bucket' => env('AWS_BUCKET'),
'url' => env('AWS_URL'),
'endpoint' => env('AWS_ENDPOINT'), // MinIO endpoint
'use_path_style_endpoint' => env('AWS_USE_PATH_STYLE_ENDPOINT', true),
'throw' => true,
],
// Dedicated bucket for sensitive KYC docs (private, encrypted-at-rest)
'kyc' => [
'driver' => 's3',
'key' => env('AWS_ACCESS_KEY_ID'),
'secret' => env('AWS_SECRET_ACCESS_KEY'),
'region' => env('AWS_DEFAULT_REGION', 'us-east-1'),
'bucket' => env('WASL_KYC_BUCKET', 'wasl-kyc'),
'url' => env('AWS_URL'),
'endpoint' => env('AWS_ENDPOINT'),
'use_path_style_endpoint' => env('AWS_USE_PATH_STYLE_ENDPOINT', true),
'throw' => true,
'visibility' => 'private',
],
],
/*
|--------------------------------------------------------------------------
| Symbolic Links
|--------------------------------------------------------------------------
*/
'links' => [
public_path('storage') => storage_path('app/public'),
],
];

57
Backend/config/jwt.php Normal file
View File

@@ -0,0 +1,57 @@
<?php
/*
* |--------------------------------------------------------------------------
* | JWT Auth (tymon/jwt-auth) configuration for WASL mobile API
* | Mobile clients receive short-lived JWT access tokens. Refresh tokens are
* | rotated and stored hashed server-side (see Security module).
* |--------------------------------------------------------------------------
*/
return [
'secret' => env('JWT_SECRET'),
// Asymmetric keys (recommended for production)
'keys' => [
'public' => env('JWT_PUBLIC_KEY'),
'private' => env('JWT_PRIVATE_KEY'),
'passphrase' => env('JWT_PASSPHRASE'),
],
'ttl' => env('JWT_TTL', 15), // 15 minutes — short-lived access token
'refresh_ttl' => env('JWT_REFRESH_TTL', 20160), // 14 days
'algo' => env('JWT_ALGO', 'HS256'),
'required_claims' => [
'iss',
'iat',
'exp',
'nbf',
'sub',
'jti',
],
'persistent_claims' => [
'dev', // device_id — bound to the token
'kyc', // kyc_level — embedded for authorization checks
],
'lock_subject' => true,
'leeway' => env('JWT_LEEWAY', 0),
'blacklist_enabled' => env('JWT_BLACKLIST_ENABLED', true),
'blacklist_grace_period' => env('JWT_BLACKLIST_GRACE_PERIOD', 0),
'decrypt_cookies' => false,
'providers' => [
'jwt' => Tymon\JWTAuth\Providers\JWT\Lcobucci::class,
'auth' => Tymon\JWTAuth\Providers\Auth\Illuminate::class,
'storage' => Tymon\JWTAuth\Providers\Storage\Illuminate::class,
],
];

View File

@@ -0,0 +1,85 @@
<?php
return [
'default' => env('LOG_CHANNEL', 'stack'),
'deprecations' => [
'channel' => env('LOG_DEPRECATIONS_CHANNEL', 'null'),
'trace' => env('LOG_DEPRECATIONS_TRACE', false),
],
'channels' => [
'stack' => [
'driver' => 'stack',
'channels' => explode(',', env('LOG_STACK', 'single')),
'ignore_exceptions' => false,
],
'single' => [
'driver' => 'single',
'path' => storage_path('logs/laravel.log'),
'level' => env('LOG_LEVEL', 'debug'),
'replace_placeholders' => true,
],
'daily' => [
'driver' => 'daily',
'path' => storage_path('logs/laravel.log'),
'level' => env('LOG_LEVEL', 'debug'),
'days' => env('LOG_DAILY_DAYS', 14),
'replace_placeholders' => true,
],
// Separate channel for financial transaction logs (longer retention)
'transactions' => [
'driver' => 'daily',
'path' => storage_path('logs/transactions.log'),
'level' => 'info',
'days' => 90,
'replace_placeholders' => true,
],
// Security events (login, failed login, fraud alerts) — long retention
'security' => [
'driver' => 'daily',
'path' => storage_path('logs/security.log'),
'level' => 'info',
'days' => 180,
'replace_placeholders' => true,
],
'stderr' => [
'driver' => 'monolog',
'level' => env('LOG_LEVEL', 'info'),
'handler' => Monolog\Handler\StreamHandler::class,
'formatter' => env('LOG_STDERR_FORMATTER'),
'with' => [
'stream' => 'php://stderr',
],
'processors' => [
\App\Logging\MaskSensitiveProcessor::class,
],
],
'syslog' => [
'driver' => 'syslog',
'level' => env('LOG_LEVEL', 'debug'),
],
'errorlog' => [
'driver' => 'errorlog',
'level' => env('LOG_LEVEL', 'debug'),
],
'null' => [
'driver' => 'monolog',
'handler' => Monolog\Handler\NullHandler::class,
],
'emergency' => [
'path' => storage_path('logs/laravel.log'),
],
],
];

146
Backend/config/octane.php Normal file
View File

@@ -0,0 +1,146 @@
<?php
use Laravel\Octane\Contracts\Operation;
use Laravel\Octane\Events\RequestHandled;
use Laravel\Octane\Events\RequestReceived;
use Laravel\Octane\Events\RequestTerminated;
use Laravel\Octane\Events\TaskReceived;
use Laravel\Octane\Events\TaskTerminated;
use Laravel\Octane\Events\TickReceived;
use Laravel\Octane\Events\TickTerminated;
use Laravel\Octane\Octane;
return [
/*
|--------------------------------------------------------------------------
| Octane Servers
|--------------------------------------------------------------------------
*/
'servers' => [
// RoadRunner via RPC socket
'roadrunner' => [
'rpc' => [
'tcp' => '127.0.0.1:7233',
],
'logs' => [
'mode' => env('OCTANE_LOGS_MODE', 'production' === env('APP_ENV') ? 'none' : 'dev'),
'level' => env('OCTANE_LOGS_LEVEL', 'info'),
],
'reload' => [
'watch' => env('OCTANE_RELOAD_WATCH', 'app,config,database,resources/views,routes'),
'interval' => env('OCTANE_RELOAD_INTERVAL', 2000),
],
'http2' => [
'enabled' => env('OCTANE_HTTP2_ENABLED', true),
],
'maxTotalWorkerMemory' => env('OCTANE_MAX_TOTAL_WORKER_MEMORY', 512), // MB — flush worker if exceeded
'worker' => [
'maxRequestCount' => env('OCTANE_WORKER_MAX_REQUEST_COUNT', 10000), // recycle to clear leaks
'maxMemoryMB' => env('OCTANE_WORKER_MAX_MEMORY_MB', 512),
],
],
// Swoole driver — recommended for WASL (best performance)
'swoole' => [
'mode' => env('OCTANE_MODE', 'SWOOLE_PROCESS'),
'options' => [
'log_file' => storage_path('logs/swoole.log'),
'package_max_length' => 8 * 1024 * 1024, // 8 MB
'worker_num' => env('OCTANE_WORKER_NUM', swoole_cpu_num()),
'task_worker_num' => env('OCTANE_TASK_WORKER_NUM', swoole_cpu_num()),
'max_request' => env('OCTANE_MAX_REQUEST', 10000),
'max_request_grace' => env('OCTANE_MAX_REQUEST_GRACE', 1000),
'max_request_execution_time' => env('OCTANE_MAX_REQUEST_EXECUTION_TIME', 30),
'reload_async' => true,
'enable_coroutine' => true,
'send_timeout' => 10,
],
'cache' => [
'rows' => env('OCTANE_CACHE_ROWS', 1000),
'bytes' => env('OCTANE_CACHE_BYTES', 10485760), // 10 MB per worker
],
'websocket' => [
'enabled' => env('OCTANE_WEBSOCKET_ENABLED', false),
],
'hot' => [
'enable' => env('OCTANE_HOT_RELOAD', 'local' === env('APP_ENV')),
'watch' => ['app', 'config', 'resources/views', 'routes'],
],
],
],
/*
|--------------------------------------------------------------------------
| Octane Cache Table
|--------------------------------------------------------------------------
*/
'cache' => [
'rows' => env('OCTANE_CACHE_ROWS', 1000),
],
/*
|--------------------------------------------------------------------------
| Octane Watchers / Listeners
| Flush state between requests to prevent leaks across requests in the
| long-running Octane process (critical for financial correctness).
|--------------------------------------------------------------------------
*/
'listeners' => [
RequestReceived::class => [
...Octane::prepareApplicationForNextOperation(),
...Octane::prepareApplicationForNextRequest(),
],
RequestHandled::class => [
//
],
RequestTerminated::class => [
// Clear request-scoped state between requests
],
TaskReceived::class => [
...Octane::prepareApplicationForNextOperation(),
],
TaskTerminated::class => [
//
],
TickReceived::class => [
...Octane::prepareApplicationForNextOperation(),
],
TickTerminated::class => [
//
],
Operation::class => [
// Octane::flushState(),
],
],
/*
|--------------------------------------------------------------------------
| Warm Specific Tags / Fifo
|--------------------------------------------------------------------------
*/
'warm' => [
...Octane::defaultServicesToWarm(),
'view' => fn ($app) => $app->make('view'),
],
/*
|--------------------------------------------------------------------------
| Garbage Collection Threshold (prevent memory leaks)
|--------------------------------------------------------------------------
*/
'garbage_collection_threshold' => env('OCTANE_GARBAGE_COLLECTION_THRESHOLD', 50),
/*
|--------------------------------------------------------------------------
| Maximum Execution Time (seconds) — financial ops should be fast
|--------------------------------------------------------------------------
*/
'max_execution_time' => env('OCTANE_MAX_EXECUTION_TIME', 30),
];

View File

@@ -0,0 +1,71 @@
<?php
use App\Enums\Role;
return [
'models' => [
/*
* When using the "HasPermissions" trait from this package, we need to
* know which entity should be used to retrieve your permissions.
*/
'permission' => Spatie\Permission\Models\Permission::class,
/*
* When using the "HasRoles" trait from this package, we need to
* know which entity should be used to retrieve your roles.
*/
'role' => Spatie\Permission\Models\Role::class,
],
'table_names' => [
'roles' => 'roles',
'permissions' => 'permissions',
'model_has_permissions' => 'model_has_permissions',
'model_has_roles' => 'model_has_roles',
'role_has_permissions' => 'role_has_permissions',
],
'column_names' => [
'role_pivot_key' => null,
'permission_pivot_key' => null,
'model_morph_key' => 'model_id',
'team_foreign_key' => 'team_id',
],
/*
* When set to true, the method for checking permissions will be registered
* on the gate. Set to false to disable.
*/
'register_permission_check_method' => true,
/*
* When set to true, required permissions will be checked on the model
* to determine access.
*/
'teams' => false,
'use_passport_client_credentials' => false,
'display_permission_in_exception' => (bool) env('APP_DEBUG', false),
'display_role_in_exception' => (bool) env('APP_DEBUG', false),
'enable_wildcard_permission' => false,
'cache' => [
'expiration_time' => \DateInterval::createFromDateString('24 hours'),
'key' => 'spatie.permission.cache',
'store' => 'default',
],
];

132
Backend/config/wasl.php Normal file
View File

@@ -0,0 +1,132 @@
<?php
/*
* |--------------------------------------------------------------------------
* | WASL — Domain-specific configuration for the wallet platform
* | Centralized business rules, limits, and feature flags. Changing behavior
* | should NEVER require a code change — only an .env flip.
* |--------------------------------------------------------------------------
*/
return [
/*
|--------------------------------------------------------------------------
| Wallet & Money
|--------------------------------------------------------------------------
*/
'wallet' => [
'default_currency' => env('WASL_DEFAULT_CURRENCY', 'SYP'),
'supported_currencies' => ['SYP', 'USD', 'EUR', 'AED'],
// Per-KYC-tier limits (in minor units of SYP)
// Tier 0: no KYC, Tier 1: phone verified, Tier 2: ID verified, Tier 3: full
'limits' => [
0 => ['balance' => 0, 'daily_tx' => 0, 'monthly_tx' => 0],
1 => ['balance' => 5000000, 'daily_tx' => 1000000, 'monthly_tx' => 20000000], // 50k / 10k / 200k SYP
2 => ['balance' => 50000000, 'daily_tx' => 10000000, 'monthly_tx' => 200000000], // 500k / 100k / 2M SYP
3 => ['balance' => 500000000, 'daily_tx' => 100000000, 'monthly_tx' => 2000000000], // 5M / 1M / 20M SYP
],
// Fees in minor units (basis points * amount, or flat)
'fees' => [
'p2p' => [
'enabled' => env('WASL_FEE_P2P_ENABLED', false),
'percent' => env('WASL_FEE_P2P_PERCENT', 0), // e.g. 0.5 = 0.5%
'flat_minor' => env('WASL_FEE_P2P_FLAT', 0),
'min_minor' => env('WASL_FEE_P2P_MIN', 0),
'max_minor' => env('WASL_FEE_P2P_MAX', 0),
],
],
],
/*
|--------------------------------------------------------------------------
| Security
|--------------------------------------------------------------------------
*/
'security' => [
'pin' => [
'min_length' => 6,
'max_attempts' => env('WASL_PIN_MAX_ATTEMPTS', 5),
'lock_minutes' => env('WASL_PIN_LOCK_MINUTES', 30),
'hash_algo' => 'argon2id',
],
'otp' => [
'length' => env('WASL_OTP_LENGTH', 6),
'ttl_seconds' => env('WASL_OTP_TTL', 300), // 5 minutes
'max_attempts' => env('WASL_OTP_MAX_ATTEMPTS', 3),
'resend_cooldown' => env('WASL_OTP_RESEND_COOLDOWN', 60), // 1 minute
],
'login' => [
'max_attempts' => env('WASL_LOGIN_MAX_ATTEMPTS', 5),
'lock_minutes' => env('WASL_LOGIN_LOCK_MINUTES', 30),
],
// Encryption keys for field-level encryption (phone, national_id, cards)
'encryption' => [
'cipher' => env('WASL_ENC_CIPHER', 'aes-256-cbc'),
// Separate key from APP_KEY to allow key rotation without re-encrypting all data
'field_key' => env('WASL_FIELD_ENCRYPTION_KEY'),
],
'idempotency' => [
'enabled' => env('WASL_IDEMPOTENCY_ENABLED', true),
'ttl_seconds' => env('WASL_IDEMPOTENCY_TTL', 86400), // 24h
],
],
/*
|--------------------------------------------------------------------------
| KYC
|--------------------------------------------------------------------------
*/
'kyc' => [
'auto_approve_in_local' => env('WASL_KYC_AUTO_APPROVE_LOCAL', false),
'max_document_size_mb' => env('WASL_KYC_MAX_DOC_MB', 5),
'allowed_mime_types' => ['image/jpeg', 'image/png', 'application/pdf'],
],
/*
|--------------------------------------------------------------------------
| Rate Limiting (per-IP / per-user)
|--------------------------------------------------------------------------
*/
'throttle' => [
'login' => ['max' => 5, 'minutes' => 1],
'otp_request' => ['max' => 3, 'minutes' => 1],
'transfer' => ['max' => 10, 'minutes' => 60],
'api' => ['max' => 60, 'minutes' => 1],
],
/*
|--------------------------------------------------------------------------
| Reference Code Format
| P2P transfer reference codes visible to users.
|--------------------------------------------------------------------------
*/
'reference' => [
'prefix' => env('WASL_REF_PREFIX', 'WASL'),
'length' => env('WASL_REF_LENGTH', 8), // alphanumeric chars after prefix
],
/*
|--------------------------------------------------------------------------
| Feature Flags
|--------------------------------------------------------------------------
*/
'features' => [
'crypto' => env('WASL_FEATURE_CRYPTO', false),
'cards' => env('WASL_FEATURE_CARDS', false),
'international' => env('WASL_FEATURE_INTERNATIONAL', false),
'merchant' => env('WASL_FEATURE_MERCHANT', true),
],
];