>|null $pending_accounts_data */ class BankingConnection extends Model { /** @use HasFactory */ use BelongsToSpace, HasFactory, HasUuids, SoftDeletes; protected $fillable = [ 'user_id', 'space_id', 'provider', 'authorization_id', 'state_token', 'session_id', 'aspsp_name', 'aspsp_country', 'aspsp_logo', 'status', 'valid_until', 'last_synced_at', 'bank_transactions_email_cutoff_at', 'error_message', 'rate_limited_until', 'consecutive_sync_failures', 'pending_accounts_data', 'api_token', 'api_secret', ]; protected $hidden = [ 'space_id', 'api_token', 'api_secret', 'pending_accounts_data', 'authorization_id', 'state_token', 'session_id', ]; protected function casts(): array { return [ 'provider' => BankingProvider::class, 'status' => BankingConnectionStatus::class, 'valid_until' => 'datetime', 'last_synced_at' => 'datetime', 'bank_transactions_email_cutoff_at' => 'datetime', 'rate_limited_until' => 'datetime', 'pending_accounts_data' => 'array', 'api_token' => 'encrypted', 'api_secret' => 'encrypted', ]; } /** @return BelongsTo */ public function user(): BelongsTo { return $this->belongsTo(User::class); } /** @return HasMany */ public function accounts(): HasMany { return $this->hasMany(Account::class); } /** @return HasMany */ public function syncLogs(): HasMany { return $this->hasMany(BankingSyncLog::class); } /** @return HasOne */ public function latestSyncLog(): HasOne { return $this->hasOne(BankingSyncLog::class)->latestOfMany(); } public function isActive(): bool { return $this->status === BankingConnectionStatus::Active; } public function isIndexaCapital(): bool { return $this->provider === BankingProvider::IndexaCapital; } public function isBinance(): bool { return $this->provider === BankingProvider::Binance; } public function isBitpanda(): bool { return $this->provider === BankingProvider::Bitpanda; } public function isCoinbase(): bool { return $this->provider === BankingProvider::Coinbase; } public function isEnableBanking(): bool { return $this->provider === BankingProvider::EnableBanking; } public function isInteractiveBrokers(): bool { return $this->provider === BankingProvider::InteractiveBrokers; } public function usesApiKey(): bool { return $this->provider->usesApiKey(); } public function isWise(): bool { return $this->provider === BankingProvider::Wise; } public function hasPendingAccounts(): bool { return ! empty($this->pending_accounts_data); } /** * Pending accounts the user can actually map. Providers sometimes report accounts * without a uid (EnableBanking does it for some French cards); those can never be * synced, so offering them for mapping only produces a validation error. * * @return array> */ public function mappablePendingAccounts(): array { return array_values(array_filter( $this->pending_accounts_data ?? [], fn (array $account): bool => ! empty($account['uid']), )); } /** * Names of the pending accounts left out of the mapping screen, so the user is told * why an account they can see in their bank never shows up here. * * @return array */ public function unmappablePendingAccountNames(): array { return array_values(array_map( fn (array $account): string => $account['name'] ?? $account['account_id']['iban'] ?? __('Bank Account'), array_filter( $this->pending_accounts_data ?? [], fn (array $account): bool => empty($account['uid']), ), )); } public function isExpired(): bool { return $this->status === BankingConnectionStatus::Expired || ($this->valid_until && $this->valid_until->isPast()); } public function isRateLimited(): bool { return $this->rate_limited_until !== null && $this->rate_limited_until->isFuture(); } }