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

PlaidService.php 8.2KB

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