Nevar pievienot vairāk kā 25 tēmas Tēmai ir jāsākas ar burtu vai ciparu, tā var saturēt domu zīmes ('-') un var būt līdz 35 simboliem gara.

PlaidService.php 8.2KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297
  1. <?php
  2. namespace App\Services;
  3. use App\Models\Company;
  4. use GuzzleHttp\Psr7\Message;
  5. use Illuminate\Contracts\Config\Repository as Config;
  6. use Illuminate\Http\Client\Factory as HttpClient;
  7. use Illuminate\Http\Client\RequestException;
  8. use Illuminate\Http\Client\Response;
  9. use Illuminate\Support\Facades\Log;
  10. use RuntimeException;
  11. class PlaidService
  12. {
  13. public const API_VERSION = '2020-09-14';
  14. protected ?string $clientId;
  15. protected ?string $clientSecret;
  16. protected ?string $environment;
  17. protected ?string $webhookUrl;
  18. protected ?string $baseUrl;
  19. protected HttpClient $client;
  20. protected Config $config;
  21. protected array $plaidSupportedLanguages = [
  22. 'da', 'nl', 'en',
  23. 'et', 'fr', 'de',
  24. 'it', 'lv', 'lt',
  25. 'no', 'pl', 'pt',
  26. 'ro', 'es', 'sv',
  27. ];
  28. protected array $plaidSupportedCountries = [
  29. 'US', 'GB', 'ES',
  30. 'NL', 'FR', 'IE',
  31. 'CA', 'DE', 'IT',
  32. 'PL', 'DK', 'NO',
  33. 'SE', 'EE', 'LT',
  34. 'LV', 'PT', 'BE',
  35. ];
  36. public function __construct(HttpClient $client, Config $config)
  37. {
  38. $this->client = $client;
  39. $this->config = $config;
  40. $this->clientId = $this->config->get('plaid.client_id');
  41. $this->clientSecret = $this->config->get('plaid.client_secret');
  42. $this->environment = $this->config->get('plaid.environment', 'sandbox');
  43. $this->webhookUrl = $this->config->get('plaid.webhook_url');
  44. $this->setBaseUrl($this->environment);
  45. }
  46. /**
  47. * Determine if the Plaid service is enabled and properly configured.
  48. */
  49. public function isEnabled(): bool
  50. {
  51. if (is_demo_environment() || empty($this->clientId) || empty($this->clientSecret)) {
  52. return false;
  53. }
  54. return true;
  55. }
  56. public function setClientCredentials(?string $clientId, ?string $clientSecret): self
  57. {
  58. $this->clientId = $clientId ?? $this->clientId;
  59. $this->clientSecret = $clientSecret ?? $this->clientSecret;
  60. return $this;
  61. }
  62. public function setEnvironment(?string $environment): self
  63. {
  64. $this->environment = $environment ?? $this->environment;
  65. $this->setBaseUrl($this->environment);
  66. return $this;
  67. }
  68. public function setBaseUrl(?string $environment): void
  69. {
  70. $this->baseUrl = match ($environment) {
  71. 'development' => 'https://development.plaid.com',
  72. 'production' => 'https://production.plaid.com',
  73. default => 'https://sandbox.plaid.com', // Default to sandbox, including if environment is null
  74. };
  75. }
  76. public function getBaseUrl(): string
  77. {
  78. return $this->baseUrl;
  79. }
  80. public function getEnvironment(): string
  81. {
  82. return $this->environment;
  83. }
  84. public function buildRequest(string $method, string $endpoint, array $data = []): Response
  85. {
  86. $request = $this->client->withHeaders([
  87. 'Plaid-Version' => self::API_VERSION,
  88. 'Content-Type' => 'application/json',
  89. ])->baseUrl($this->baseUrl);
  90. if ($method === 'post') {
  91. $request = $request->withHeaders([
  92. 'PLAID-CLIENT-ID' => $this->clientId,
  93. 'PLAID-SECRET' => $this->clientSecret,
  94. ]);
  95. }
  96. return $request->{$method}($endpoint, $data);
  97. }
  98. public function sendRequest(string $endpoint, array $data = []): object
  99. {
  100. try {
  101. $response = $this->buildRequest('post', $endpoint, $data)->throw()->object();
  102. if ($response === null) {
  103. throw new RuntimeException('Plaid API returned null response.');
  104. }
  105. return $response;
  106. } catch (RequestException $e) {
  107. $statusCode = $e->response->status();
  108. $message = "Plaid API request returned status code {$statusCode}";
  109. $summary = Message::bodySummary($e->response->toPsrResponse(), 1000);
  110. if ($summary !== null) {
  111. $message .= ":\n{$summary}\n";
  112. }
  113. Log::error($message);
  114. throw new RuntimeException('An error occurred while communicating with the Plaid API.');
  115. }
  116. }
  117. public function createPlaidUser(Company $company): array
  118. {
  119. return array_filter([
  120. 'client_user_id' => (string) $company->owner->id,
  121. 'legal_name' => $company->owner->name,
  122. 'phone_number' => $company->profile->phone_number,
  123. 'email_address' => $company->owner->email,
  124. ], static fn ($value): bool => $value !== null);
  125. }
  126. public function getLanguage(string $language): string
  127. {
  128. if (in_array($language, $this->plaidSupportedLanguages, true)) {
  129. return $language;
  130. }
  131. return 'en';
  132. }
  133. public function getCountry(?string $country = null): string
  134. {
  135. if (in_array($country, $this->plaidSupportedCountries, true)) {
  136. return $country;
  137. }
  138. return 'US';
  139. }
  140. public function createToken(string $language, string $country, array $user, array $products = []): object
  141. {
  142. $plaidLanguage = $this->getLanguage($language);
  143. $plaidCountry = $this->getCountry($country);
  144. return $this->createLinkToken(
  145. 'ERPSAAS',
  146. $plaidLanguage,
  147. [$plaidCountry],
  148. $user,
  149. $products,
  150. );
  151. }
  152. public function createLinkToken(string $clientName, string $language, array $countryCodes, array $user, array $products): object
  153. {
  154. $data = [
  155. 'client_name' => $clientName,
  156. 'language' => $language,
  157. 'country_codes' => $countryCodes,
  158. 'user' => (object) $user,
  159. ];
  160. if ($products) {
  161. $data['products'] = $products;
  162. }
  163. if (! empty($this->webhookUrl)) {
  164. $data['webhook'] = $this->webhookUrl;
  165. }
  166. return $this->sendRequest('link/token/create', $data);
  167. }
  168. public function exchangePublicToken(string $publicToken): object
  169. {
  170. $data = [
  171. 'public_token' => $publicToken,
  172. ];
  173. return $this->sendRequest('item/public_token/exchange', $data);
  174. }
  175. public function getAccounts(string $accessToken, array $options = []): object
  176. {
  177. $data = [
  178. 'access_token' => $accessToken,
  179. 'options' => (object) $options,
  180. ];
  181. return $this->sendRequest('accounts/get', $data);
  182. }
  183. public function getInstitution(string $institutionId, string $country): object
  184. {
  185. $options = [
  186. 'include_optional_metadata' => true,
  187. ];
  188. $plaidCountry = $this->getCountry($country);
  189. return $this->getInstitutionById($institutionId, [$plaidCountry], $options);
  190. }
  191. public function getInstitutionById(string $institutionId, array $countryCodes, array $options = []): object
  192. {
  193. $data = [
  194. 'institution_id' => $institutionId,
  195. 'country_codes' => $countryCodes,
  196. 'options' => (object) $options,
  197. ];
  198. return $this->sendRequest('institutions/get_by_id', $data);
  199. }
  200. public function getTransactions(string $accessToken, string $startDate, string $endDate, array $options = []): object
  201. {
  202. $data = [
  203. 'access_token' => $accessToken,
  204. 'start_date' => $startDate,
  205. 'end_date' => $endDate,
  206. 'options' => (object) $options,
  207. ];
  208. return $this->sendRequest('transactions/get', $data);
  209. }
  210. public function fireSandboxWebhook(string $accessToken, string $webhookCode, string $webhookType): object
  211. {
  212. $data = [
  213. 'access_token' => $accessToken,
  214. 'webhook_code' => $webhookCode,
  215. 'webhook_type' => $webhookType,
  216. ];
  217. return $this->sendRequest('sandbox/item/fire_webhook', $data);
  218. }
  219. public function refreshTransactions(string $accessToken): object
  220. {
  221. $data = [
  222. 'access_token' => $accessToken,
  223. ];
  224. return $this->sendRequest('transactions/refresh', $data);
  225. }
  226. public function removeItem(string $accessToken): object
  227. {
  228. $data = [
  229. 'access_token' => $accessToken,
  230. ];
  231. return $this->sendRequest('item/remove', $data);
  232. }
  233. }