TossTrait.php 8.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228
  1. <?php
  2. namespace App\Http\Traits;
  3. use Illuminate\Http\Request;
  4. use Illuminate\Support\Facades\Http;
  5. use Illuminate\Support\Facades\Cookie;
  6. use App\Models\DTO\Toss\AuthData;
  7. use App\Models\DTO\Toss\CancelData;
  8. trait TossTrait
  9. {
  10. use CommonTrait;
  11. /**
  12. * 토스 API Client Key
  13. */
  14. protected function getClientKey(): string
  15. {
  16. return (config('use_pg_test', 0) ? config('test_client_key') : config('live_client_key'));
  17. }
  18. /**
  19. * 토스 API Secret Key
  20. */
  21. protected function getSecretKey(): string
  22. {
  23. return (config('use_pg_test', 0) ? config('test_secret_key') : config('live_secret_key'));
  24. }
  25. /**
  26. * 토스 인증 일련번호
  27. */
  28. protected function getAuthorization(): string
  29. {
  30. return ('Basic ' . base64_encode($this->getSecretKey() . ':'));
  31. }
  32. /**
  33. * 토스 실 본인인증 사용 여부
  34. */
  35. protected function getTossCertIsTest(): bool
  36. {
  37. return config('toss_cert_is_test', 0);
  38. }
  39. /**
  40. * 토스 카드사 정보
  41. */
  42. protected function getCards(): array
  43. {
  44. return [
  45. '3K' => '기업비씨', '46' => '광주', '71' => '롯데', '30' => '산업',
  46. '31' => 'BC카드', '51' => '삼성카드', '38' => '새마을', '41' => '신한',
  47. '62' => '신협', '36' => '씨티', '33' => '우리', 'W1' => '우리',
  48. '37' => '우체국', '39' => '저축', '35' => '전북', '42' => '제주',
  49. '15' => '카카오뱅크', '3A' => '케이뱅크', '24' => '토스뱅크', '21' => '하나',
  50. '61' => '현대', '11' => '국민', '91' => '농협', '34' => '수협'
  51. ];
  52. }
  53. /**
  54. * 토스 은행/증권사 정보
  55. */
  56. protected function getBanks(): array
  57. {
  58. return [
  59. '39' => '경남은행', '34' => '광주은행', 'S8' => '교보증권', '12' => '단위농협(지역농축협)',
  60. 'SE' => '대신증권', 'SK' => '메리츠증권', 'S5' => '미래에셋증권', 'SM' => '부국증권',
  61. '32' => '부산은행', 'S3' => '삼성증권', '45' => '새마을금고', '64' => '산림조합',
  62. 'SN' => '신영증권', 'S2' => '신한금융투자', '88' => '신한은행', '48' => '신협',
  63. '27' => '씨티은행', '20' => '우리은행', '71' => '우체국예금보험', 'S0' => '유안타증권',
  64. 'SJ' => '유진투자증권', '50' => '저축은행중앙회', '37' => '전북은행', '35' => '제주은행',
  65. '90' => '카카오뱅크', '288' => '카카오페이증권', '89' => '케이뱅크', '92' => '토스뱅크',
  66. 'ST' => '토스증권', 'SR' => '펀드온라인코리아(한국포스증권)', 'SH' => '하나금융투자', '81' => '하나은행',
  67. 'S9' => '하이투자증권', 'S6' => '한국투자증권', 'SG' => '한화투자증권', 'SA' => '현대차증권',
  68. '54' => '홍콩상하이은행', 'SI' => 'DB금융투자', '31' => 'DGB대구은행', '03' => 'IBK기업은행',
  69. '06' => 'KB국민은행', 'S4' => 'KB증권', '02' => 'KDB산업은행', 'SP' => 'KTB투자증권(다올투자증권)',
  70. 'SO' => 'LIG투자증권', '11' => 'NH농협은행', 'SL' => 'NH투자증권', '23' => 'SC제일은행',
  71. '07' => 'Sh수협은행', 'SD' => 'SK증권'
  72. ];
  73. }
  74. /**
  75. * 카드 혜택 조회
  76. */
  77. protected function requestCardPromotions(): mixed
  78. {
  79. $response = Http::withHeaders([
  80. 'Authorization' => $this->getAuthorization(),
  81. ])->get('https://api.tosspayments.com/v1/promotions/card');
  82. $res = $response->json();
  83. if (isset($res['interestFreeCards']) > 0) {
  84. $interestFreeCards = $res['interestFreeCards'];
  85. foreach ($interestFreeCards as $i => $row) {
  86. $res['interestFreeCards'][$i]['min'] = min($row['installmentFreeMonths']);
  87. $res['interestFreeCards'][$i]['max'] = max($row['installmentFreeMonths']);
  88. $res['interestFreeCards'][$i]['minimumPaymentAmountKor'] = $this->numberToHangul($row['minimumPaymentAmount']);
  89. }
  90. }
  91. return $res;
  92. }
  93. /**
  94. * 결제 승인 API 호출
  95. */
  96. protected function requestPayment(string $paymentKey, string $orderId, int $amount): mixed
  97. {
  98. // 기본 결제 요청
  99. $response = Http::withHeaders([
  100. 'Authorization' => $this->getAuthorization(),
  101. 'Content-Type' => 'application/json'
  102. ])->post('https://api.tosspayments.com/v1/payments/confirm', [
  103. 'paymentKey' => $paymentKey,
  104. 'orderId' => $orderId,
  105. 'amount' => $amount
  106. ]);
  107. return $response->json();
  108. }
  109. /**
  110. * 결제 취소 API 호출
  111. */
  112. protected function requestCancel(string $paymentKey, string $idempotencyKey, CancelData $cancelData): mixed
  113. {
  114. // 기본 결제 요청
  115. $response = Http::withHeaders([
  116. 'Authorization' => $this->getAuthorization(),
  117. 'Content-Type' => 'application/json',
  118. 'Idempotency-Key' => $idempotencyKey,
  119. ])->post(sprintf('https://api.tosspayments.com/v1/payments/%s/cancel', $paymentKey),
  120. $cancelData->toArray()
  121. );
  122. return $response->json();
  123. }
  124. /**
  125. * 본인확인 Access Token 요청
  126. */
  127. protected function requestAccessToken(Request $request): ?array
  128. {
  129. $tossCertIsTest = $this->getTossCertIsTest();
  130. if (!$request->hasCookie('tossAccessToken')) {
  131. $token = Http::asForm()->post('https://oauth2.cert.toss.im/token', [
  132. 'grant_type' => 'client_credentials',
  133. 'client_id' => ($tossCertIsTest ? config('test_toss_cert_client_id') : config('live_toss_cert_client_id')),
  134. 'client_secret' => ($tossCertIsTest ? config('test_toss_cert_client_secret') : config('live_toss_cert_client_secret')),
  135. 'scope' => 'ca'
  136. ])->json();
  137. } else {
  138. $token = unserialize($request->cookie('tossAccessToken'));
  139. if ($token['tossCertIsTest'] != $tossCertIsTest) {
  140. Cookie::expire('tossAccessToken');
  141. $token = $this->requestAccessToken($request);
  142. }
  143. }
  144. return ($token + ['tossCertIsTest' => $tossCertIsTest]);
  145. }
  146. /**
  147. * 본인확인 요청
  148. */
  149. protected function requestUserAuth(array $token, AuthData $authData): object|array|null
  150. {
  151. return Http::asJson()->withHeaders([
  152. 'Authorization' => sprintf("%s %s", $token['token_type'], $token['access_token'])
  153. ])->post('https://cert.toss.im/api/v2/sign/user/auth/id/request', $authData->toArray())->object();
  154. }
  155. /**
  156. * 본인확인 상태 조회
  157. */
  158. protected function requestUserAuthStatus(array $token, string $txId): object|array|null
  159. {
  160. return Http::asJson()->withHeaders([
  161. 'Authorization' => sprintf("%s %s", $token['token_type'], $token['access_token'])
  162. ])->post('https://cert.toss.im/api/v2/sign/user/auth/id/status', [
  163. 'txId' => $txId,
  164. ])->object();
  165. }
  166. /**
  167. * 본인확인 결과 확인
  168. */
  169. protected function requestCertResult(array $token, string $txId, string $sessionKey): object|array|null
  170. {
  171. return Http::asJson()->withHeaders([
  172. 'Authorization' => sprintf("%s %s", $token['token_type'], $token['access_token'])
  173. ])->post('https://cert.toss.im/api/v2/sign/user/auth/id/result', [
  174. 'txId' => $txId,
  175. 'sessionKey' => $sessionKey
  176. ])->object();
  177. }
  178. /**
  179. * 본인확인 결과 조회
  180. */
  181. protected function getTossCertResult(array $token, string $txId): ?object
  182. {
  183. $sessionId = $this->generateSessionId();
  184. $secretKey = $this->generateRandomBytes(32);
  185. $iv = $this->generateRandomBytes(12);
  186. $sessionKey = $this->generateSessionKey($sessionId, $secretKey, $iv);
  187. $result = $this->requestCertResult($token, $txId, $sessionKey);
  188. if ($result->resultType == 'FAIL') {
  189. return $result;
  190. }
  191. $personalData = $result->success->personalData;
  192. $result->success->personalData = (object)[
  193. 'ci' => $this->decryptData($secretKey, $iv, $personalData->ci),
  194. 'name' => $this->decryptData($secretKey, $iv, $personalData->name),
  195. 'birthday' => $this->decryptData($secretKey, $iv, $personalData->birthday),
  196. 'gender' => $this->decryptData($secretKey, $iv, $personalData->gender),
  197. 'nationality' => $this->decryptData($secretKey, $iv, $personalData->nationality),
  198. 'di' => $this->decryptData($secretKey, $iv, $personalData->di)
  199. ];
  200. return $result;
  201. }
  202. }