選択できるのは25トピックまでです。 トピックは、先頭が英数字で、英数字とダッシュ('-')を使用した35文字以内のものにしてください。

PopulateAccountFromPlaid.php 6.0KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177
  1. <?php
  2. namespace App\Listeners;
  3. use App\Events\PlaidSuccess;
  4. use App\Models\Accounting\AccountSubtype;
  5. use App\Models\Banking\BankAccount;
  6. use App\Models\Banking\Institution;
  7. use App\Models\Company;
  8. use App\Models\Setting\Currency;
  9. use App\Services\PlaidService;
  10. use App\Utilities\Currency\CurrencyAccessor;
  11. use Illuminate\Support\Facades\DB;
  12. class PopulateAccountFromPlaid
  13. {
  14. protected PlaidService $plaid;
  15. /**
  16. * Create the event listener.
  17. */
  18. public function __construct(PlaidService $plaid)
  19. {
  20. $this->plaid = $plaid;
  21. }
  22. /**
  23. * Handle the event.
  24. */
  25. public function handle(PlaidSuccess $event): void
  26. {
  27. DB::transaction(function () use ($event) {
  28. $this->processPlaidSuccess($event);
  29. });
  30. }
  31. public function processPlaidSuccess(PlaidSuccess $event): void
  32. {
  33. $accessToken = $event->accessToken;
  34. $company = $event->company;
  35. $authResponse = $this->plaid->getAccounts($accessToken);
  36. $institutionResponse = $this->plaid->getInstitution($authResponse->item->institution_id, $company->profile->country);
  37. $this->processInstitution($authResponse, $institutionResponse, $company);
  38. }
  39. public function processInstitution($authResponse, $institutionResponse, Company $company): void
  40. {
  41. $institution = Institution::updateOrCreate([
  42. 'external_institution_id' => $authResponse->item->institution_id ?? null,
  43. ], [
  44. 'name' => $institutionResponse->institution->name ?? null,
  45. 'logo' => $institutionResponse->institution->logo ?? null,
  46. 'website' => $institutionResponse->institution->url ?? null,
  47. ]);
  48. foreach ($authResponse->accounts as $plaidAccount) {
  49. $this->processBankAccount($plaidAccount, $company, $institution, $authResponse);
  50. }
  51. }
  52. public function processBankAccount($plaidAccount, Company $company, Institution $institution, $authResponse): void
  53. {
  54. $identifierHash = md5($institution->external_institution_id . $plaidAccount->name . $plaidAccount->mask);
  55. $bankAccount = BankAccount::updateOrCreate([
  56. 'company_id' => $company->id,
  57. 'identifier' => $identifierHash,
  58. ], [
  59. 'is_connected_account' => true,
  60. 'external_account_id' => $plaidAccount->account_id,
  61. 'item_id' => $authResponse->item->item_id,
  62. 'enabled' => BankAccount::where('company_id', $company->id)->where('enabled', true)->doesntExist(),
  63. 'type' => $plaidAccount->type,
  64. 'number' => $plaidAccount->mask,
  65. 'institution_id' => $institution->id,
  66. ]);
  67. $this->mapAccountDetails($bankAccount, $plaidAccount, $company);
  68. }
  69. public function mapAccountDetails(BankAccount $bankAccount, $plaidAccount, Company $company): void
  70. {
  71. $this->ensureCurrencyExists($company->id, $plaidAccount->balances->iso_currency_code);
  72. $accountSubtype = $this->getAccountSubtype($plaidAccount->type);
  73. $accountSubtypeId = $this->resolveAccountSubtypeId($company, $accountSubtype);
  74. $bankAccount->account()->updateOrCreate([], [
  75. 'name' => $plaidAccount->name,
  76. 'currency_code' => $plaidAccount->balances->iso_currency_code,
  77. 'description' => $plaidAccount->official_name ?? $plaidAccount->name,
  78. 'subtype_id' => $accountSubtypeId,
  79. 'active' => true,
  80. ]);
  81. }
  82. public function getAccountSubtype(string $plaidType): string
  83. {
  84. return match ($plaidType) {
  85. 'depository' => 'Cash and Cash Equivalents',
  86. 'credit' => 'Short-Term Borrowings',
  87. 'loan' => 'Long-Term Borrowings',
  88. 'investment' => 'Long-Term Investments',
  89. 'other' => 'Other Current Assets',
  90. };
  91. }
  92. public function resolveAccountSubtypeId(Company $company, string $accountSubtype): int
  93. {
  94. return AccountSubtype::where('company_id', $company->id)
  95. ->where('name', $accountSubtype)
  96. ->value('id');
  97. }
  98. public function ensureCurrencyExists(int $companyId, string $currencyCode): void
  99. {
  100. $defaultCurrency = CurrencyAccessor::getDefaultCurrency();
  101. $hasDefaultCurrency = $defaultCurrency !== null;
  102. $currency_code = currency($currencyCode);
  103. Currency::firstOrCreate([
  104. 'company_id' => $companyId,
  105. 'code' => $currencyCode,
  106. ], [
  107. 'name' => $currency_code->getName(),
  108. 'rate' => $currency_code->getRate(),
  109. 'precision' => $currency_code->getPrecision(),
  110. 'symbol' => $currency_code->getSymbol(),
  111. 'symbol_first' => $currency_code->isSymbolFirst(),
  112. 'decimal_mark' => $currency_code->getDecimalMark(),
  113. 'thousands_separator' => $currency_code->getThousandsSeparator(),
  114. 'enabled' => ! $hasDefaultCurrency,
  115. ]);
  116. }
  117. public function getRoutingNumber($accountId, $numbers): array
  118. {
  119. foreach ($numbers as $type => $numberList) {
  120. foreach ($numberList as $number) {
  121. if ($number->account_id === $accountId) {
  122. return match ($type) {
  123. 'ach' => ['routing_number' => $number->routing],
  124. 'bacs' => ['routing_number' => $number->sort_code],
  125. 'eft' => ['routing_number' => $number->branch],
  126. 'international' => [
  127. 'bic' => $number->bic,
  128. 'iban' => $number->iban,
  129. ],
  130. default => [],
  131. };
  132. }
  133. }
  134. }
  135. return [];
  136. }
  137. public function getFullAccountNumber($accountId, $numbers)
  138. {
  139. foreach ($numbers as $numberList) {
  140. foreach ($numberList as $number) {
  141. if ($number->account_id === $accountId && property_exists($number, 'account')) {
  142. return $number->account;
  143. }
  144. }
  145. }
  146. return null;
  147. }
  148. }