'사업자번호가 설정되지 않았거나 유효하지 않습니다.', -11102 => 'CERTKEY가 유효하지 않습니다.', -11103 => '인증서가 만료되었거나 유효하지 않습니다.', -11104 => '해당 사업자가 등록되어 있지 않습니다.', -11105 => '이미 등록된 사업자입니다.', -11106 => '아이디가 이미 존재합니다.', -11201 => '필수 파라미터가 누락되었습니다.', // 인증서 관련 -26001 => '공동인증서가 등록되어 있지 않습니다.', // 회원 관련 오류 (RegistCorp) -32000 => '알 수 없는 오류가 발생했습니다.', -32001 => '사업자번호가 유효하지 않습니다.', -32002 => '아이디가 유효하지 않습니다.', -32003 => '비밀번호가 유효하지 않습니다.', -32004 => '상호명이 유효하지 않습니다.', -32005 => '대표자명이 유효하지 않습니다.', -32006 => '이메일 형식이 유효하지 않습니다.', -32010 => '이미 등록된 사업자번호입니다.', -32011 => '이미 등록된 아이디입니다.', -32012 => '이미 등록된 아이디입니다. 다른 아이디를 사용해주세요.', -32013 => '비밀번호 형식이 유효하지 않습니다. (영문/숫자/특수문자 조합 8자리 이상)', -32014 => '연락처 형식이 유효하지 않습니다.', -32020 => '파트너 사업자번호가 유효하지 않습니다.', -32021 => '파트너 인증키(CERTKEY)가 유효하지 않습니다.', // 기타 -99999 => '서버 내부 오류가 발생했습니다.', ]; public function __construct() { // 기본값: .env 설정 사용 $this->isTestMode = config('services.barobill.test_mode', true); $this->initializeConfig(); } /** * 서버 모드 전환 (회원사별 설정 적용) * * @param bool $isTestMode true: 테스트서버, false: 운영서버 */ public function switchServerMode(bool $isTestMode): self { if ($this->isTestMode !== $isTestMode) { $this->isTestMode = $isTestMode; // SOAP 클라이언트 초기화 (새 서버로 재연결) $this->corpStateClient = null; $this->tiClient = null; $this->bankAccountClient = null; $this->cardClient = null; $this->kakaotalkClient = null; // 설정 재로드 $this->initializeConfig(); } return $this; } /** * 서버 모드 문자열로 전환 * * @param string $mode 'test' 또는 'production' */ public function setServerMode(string $mode): self { return $this->switchServerMode($mode === 'test'); } /** * 현재 서버 모드 조회 */ public function getServerMode(): string { return $this->isTestMode ? 'test' : 'production'; } /** * 설정 초기화 (서버 모드에 따른 설정 로드) */ protected function initializeConfig(): void { // DB에서 활성화된 설정 가져오기 (우선순위) $dbConfig = $this->loadConfigFromDatabase(); if ($dbConfig) { $this->certKey = $dbConfig->cert_key; $this->corpNum = $dbConfig->corp_num ?? ''; $this->soapUrls = $this->buildSoapUrls($dbConfig->base_url); } else { // DB 설정이 없으면 .env에서 가져오기 (fallback) // 테스트 모드에 따라 적절한 CERT_KEY 선택 $this->certKey = $this->isTestMode ? config('services.barobill.cert_key_test', '') : config('services.barobill.cert_key_prod', ''); $this->corpNum = config('services.barobill.corp_num', ''); $baseUrl = $this->isTestMode ? 'https://testws.baroservice.com' : 'https://ws.baroservice.com'; $this->soapUrls = [ 'corpstate' => $baseUrl.'/CORPSTATE.asmx?WSDL', 'ti' => $baseUrl.'/TI.asmx?WSDL', 'bankaccount' => $baseUrl.'/BANKACCOUNT.asmx?WSDL', 'card' => $baseUrl.'/CARD.asmx?WSDL', 'kakaotalk' => $baseUrl.'/KAKAOTALK.asmx?WSDL', 'sms' => $baseUrl.'/SMS.asmx?WSDL', ]; } } /** * DB에서 활성화된 설정 가져오기 */ protected function loadConfigFromDatabase(): ?BarobillConfig { try { return BarobillConfig::getActive($this->isTestMode); } catch (\Exception $e) { Log::warning('바로빌 DB 설정 로드 실패', ['error' => $e->getMessage()]); return null; } } /** * SOAP URL 빌드 */ protected function buildSoapUrls(string $baseUrl): array { // URL에서 후행 슬래시 제거 $baseUrl = rtrim($baseUrl, '/'); return [ 'corpstate' => $baseUrl.'/CORPSTATE.asmx?WSDL', 'ti' => $baseUrl.'/TI.asmx?WSDL', 'bankaccount' => $baseUrl.'/BANKACCOUNT.asmx?WSDL', 'card' => $baseUrl.'/CARD.asmx?WSDL', 'kakaotalk' => $baseUrl.'/KAKAOTALK.asmx?WSDL', 'sms' => $baseUrl.'/SMS.asmx?WSDL', ]; } /** * CORPSTATE SOAP 클라이언트 가져오기 (회원관리용) */ protected function getCorpStateClient(): ?SoapClient { if ($this->corpStateClient === null) { try { $this->corpStateClient = new SoapClient($this->soapUrls['corpstate'], [ 'trace' => true, 'encoding' => 'UTF-8', 'exceptions' => true, 'connection_timeout' => 30, 'cache_wsdl' => WSDL_CACHE_NONE, ]); } catch (Throwable $e) { Log::error('바로빌 CORPSTATE SOAP 클라이언트 생성 실패', [ 'error' => $e->getMessage(), 'url' => $this->soapUrls['corpstate'], ]); return null; } } return $this->corpStateClient; } /** * BANKACCOUNT SOAP 클라이언트 가져오기 (계좌조회용) */ protected function getBankAccountClient(): ?SoapClient { if ($this->bankAccountClient === null) { try { $this->bankAccountClient = new SoapClient($this->soapUrls['bankaccount'], [ 'trace' => true, 'encoding' => 'UTF-8', 'exceptions' => true, 'connection_timeout' => 30, 'cache_wsdl' => WSDL_CACHE_NONE, ]); } catch (Throwable $e) { Log::error('바로빌 BANKACCOUNT SOAP 클라이언트 생성 실패', [ 'error' => $e->getMessage(), 'url' => $this->soapUrls['bankaccount'], ]); return null; } } return $this->bankAccountClient; } /** * CARD SOAP 클라이언트 가져오기 (카드조회용) */ protected function getCardClient(): ?SoapClient { if ($this->cardClient === null) { try { $this->cardClient = new SoapClient($this->soapUrls['card'], [ 'trace' => true, 'encoding' => 'UTF-8', 'exceptions' => true, 'connection_timeout' => 30, 'cache_wsdl' => WSDL_CACHE_NONE, ]); } catch (Throwable $e) { Log::error('바로빌 CARD SOAP 클라이언트 생성 실패', [ 'error' => $e->getMessage(), 'url' => $this->soapUrls['card'], ]); return null; } } return $this->cardClient; } /** * TI SOAP 클라이언트 가져오기 (전자세금계산서용) */ protected function getTiClient(): ?SoapClient { if ($this->tiClient === null) { try { $this->tiClient = new SoapClient($this->soapUrls['ti'], [ 'trace' => true, 'encoding' => 'UTF-8', 'exceptions' => true, 'connection_timeout' => 30, 'cache_wsdl' => WSDL_CACHE_NONE, ]); } catch (Throwable $e) { Log::error('바로빌 TI SOAP 클라이언트 생성 실패', [ 'error' => $e->getMessage(), 'url' => $this->soapUrls['ti'], ]); return null; } } return $this->tiClient; } /** * SOAP 메서드 호출 */ protected function call(string $service, string $method, array $params = []): array { $client = match ($service) { 'corpstate' => $this->getCorpStateClient(), 'ti' => $this->getTiClient(), 'bankaccount' => $this->getBankAccountClient(), 'card' => $this->getCardClient(), 'kakaotalk' => $this->getKakaotalkClient(), 'sms' => $this->getSmsClient(), default => null, }; if (! $client) { return [ 'success' => false, 'error' => "SOAP 클라이언트가 초기화되지 않았습니다. ({$service})", 'error_code' => -1, ]; } if (empty($this->certKey)) { return [ 'success' => false, 'error' => 'CERTKEY가 설정되지 않았습니다. .env 파일의 BAROBILL_CERT_KEY를 확인하세요.', 'error_code' => -2, ]; } // CERTKEY 자동 추가 if (! isset($params['CERTKEY'])) { $params['CERTKEY'] = $this->certKey; } try { Log::info('바로빌 API 호출', [ 'service' => $service, 'method' => $method, 'test_mode' => $this->isTestMode, ]); $result = $client->$method($params); $resultProperty = $method.'Result'; if (isset($result->$resultProperty)) { $resultData = $result->$resultProperty; // 음수는 에러 코드 if (is_numeric($resultData) && $resultData < 0) { $errorMessage = $this->errorMessages[$resultData] ?? "바로빌 API 오류 코드: {$resultData}"; Log::error('바로빌 API 오류', [ 'method' => $method, 'error_code' => $resultData, 'error_message' => $errorMessage, ]); return [ 'success' => false, 'error' => $errorMessage, 'error_code' => $resultData, ]; } // 양수(또는 0)는 성공 return [ 'success' => true, 'data' => $resultData, ]; } return [ 'success' => true, 'data' => $result, ]; } catch (SoapFault $e) { Log::error('바로빌 SOAP 오류', [ 'method' => $method, 'fault_code' => $e->faultcode ?? null, 'fault_string' => $e->faultstring ?? null, ]); return [ 'success' => false, 'error' => 'SOAP 오류: '.$e->getMessage(), 'error_code' => $e->getCode(), ]; } catch (Throwable $e) { Log::error('바로빌 API 호출 오류', [ 'method' => $method, 'error' => $e->getMessage(), ]); return [ 'success' => false, 'error' => 'API 호출 오류: '.$e->getMessage(), 'error_code' => -999, ]; } } /** * 회원사 등록 (RegistCorp) * * @see https://ws.baroservice.com/CORPSTATE.asmx?op=RegistCorp */ public function registCorp(array $data): array { $params = [ 'CorpNum' => $this->formatBizNo($data['biz_no']), 'CorpName' => $data['corp_name'], 'CEOName' => $data['ceo_name'], 'BizType' => $data['biz_type'] ?? '', 'BizClass' => $data['biz_class'] ?? '', 'PostNum' => $data['post_num'] ?? '', 'Addr1' => $data['addr'] ?? '', 'Addr2' => '', 'MemberName' => $data['manager_name'] ?? '', 'JuminNum' => '', // 주민번호 (필요시) 'ID' => $data['barobill_id'], 'PWD' => $data['barobill_pwd'], 'Grade' => '2', // 1: 파트너, 2: 회원사 'TEL' => $data['tel'] ?? '', 'HP' => $data['manager_hp'] ?? '', 'Email' => $data['manager_email'] ?? '', ]; return $this->call('corpstate', 'RegistCorp', $params); } /** * 회원사 정보 수정 (UpdateCorpInfo) */ public function updateCorpInfo(array $data): array { $params = [ 'CorpNum' => $this->formatBizNo($data['biz_no']), 'CorpName' => $data['corp_name'], 'CEOName' => $data['ceo_name'], 'BizType' => $data['biz_type'] ?? '', 'BizClass' => $data['biz_class'] ?? '', 'PostNum' => $data['post_num'] ?? '', 'Addr1' => $data['addr'] ?? '', 'Addr2' => '', 'MemberName' => $data['manager_name'] ?? '', 'TEL' => $data['tel'] ?? '', 'HP' => $data['manager_hp'] ?? '', 'Email' => $data['manager_email'] ?? '', ]; return $this->call('corpstate', 'UpdateCorpInfo', $params); } /** * 회원사 상태 조회 (GetCorpState) */ public function getCorpState(string $corpNum): array { $params = [ 'CorpNum' => $this->formatBizNo($corpNum), ]; return $this->call('corpstate', 'GetCorpState', $params); } /** * 사업자번호 포맷팅 (하이픈 제거) */ protected function formatBizNo(string $bizNo): string { return preg_replace('/[^0-9]/', '', $bizNo); } // ======================================== // 계좌조회 서비스 (BANKACCOUNT) // ======================================== /** * 계좌조회 신청 URL 조회 * * 회원사가 계좌를 등록할 수 있는 바로빌 페이지 URL을 반환합니다. * 이 URL로 리다이렉트하면 사용자가 직접 계좌를 등록할 수 있습니다. * * @param string $corpNum 회원사 사업자번호 * @param string $userId 회원사 사용자 ID * @param string $userPwd 회원사 사용자 비밀번호 */ public function getBankAccountScrapRequestUrl(string $corpNum, string $userId, string $userPwd): array { $params = [ 'CorpNum' => $this->formatBizNo($corpNum), 'ID' => $userId, 'PWD' => $userPwd, ]; return $this->call('bankaccount', 'GetBankAccountScrapRequestURL', $params); } /** * 계좌번호 관리 URL 조회 * * 회원사가 등록된 계좌를 관리할 수 있는 바로빌 페이지 URL을 반환합니다. * * @param string $corpNum 회원사 사업자번호 * @param string $userId 회원사 사용자 ID * @param string $userPwd 회원사 사용자 비밀번호 */ public function getBankAccountManagementUrl(string $corpNum, string $userId, string $userPwd): array { $params = [ 'CorpNum' => $this->formatBizNo($corpNum), 'ID' => $userId, 'PWD' => $userPwd, ]; return $this->call('bankaccount', 'GetBankAccountManagementURL', $params); } /** * 입출금내역 URL 조회 * * 회원사가 입출금내역을 조회할 수 있는 바로빌 페이지 URL을 반환합니다. * * @param string $corpNum 회원사 사업자번호 * @param string $userId 회원사 사용자 ID * @param string $userPwd 회원사 사용자 비밀번호 */ public function getBankAccountLogUrl(string $corpNum, string $userId, string $userPwd): array { $params = [ 'CorpNum' => $this->formatBizNo($corpNum), 'ID' => $userId, 'PWD' => $userPwd, ]; return $this->call('bankaccount', 'GetBankAccountLogURL', $params); } /** * 인증서 등록 URL 조회 * * 회원사가 공동인증서를 등록할 수 있는 바로빌 페이지 URL을 반환합니다. * 세금계산서 발행 시 필요합니다. * * @param string $corpNum 회원사 사업자번호 * @param string $userId 회원사 사용자 ID * @param string $userPwd 회원사 사용자 비밀번호 */ public function getCertificateRegistUrl(string $corpNum, string $userId, string $userPwd): array { $params = [ 'CorpNum' => $this->formatBizNo($corpNum), 'ID' => $userId, 'PWD' => $userPwd, ]; return $this->call('bankaccount', 'GetCertificateRegistURL', $params); } /** * 인증서 등록일 조회 * * @param string $corpNum 회원사 사업자번호 */ public function getCertificateRegistDate(string $corpNum): array { $params = [ 'CorpNum' => $this->formatBizNo($corpNum), ]; return $this->call('bankaccount', 'GetCertificateRegistDate', $params); } /** * 인증서 만료일 조회 * * @param string $corpNum 회원사 사업자번호 */ public function getCertificateExpireDate(string $corpNum): array { $params = [ 'CorpNum' => $this->formatBizNo($corpNum), ]; return $this->call('bankaccount', 'GetCertificateExpireDate', $params); } /** * 인증서 유효성 확인 * * @param string $corpNum 회원사 사업자번호 */ public function checkCertificateValid(string $corpNum): array { $params = [ 'CorpNum' => $this->formatBizNo($corpNum), ]; return $this->call('bankaccount', 'CheckCERTIsValid', $params); } /** * 등록계좌 목록 조회 * * @param string $corpNum 회원사 사업자번호 * @param bool $availOnly 사용 가능한 계좌만 조회 여부 */ public function getBankAccounts(string $corpNum, bool $availOnly = true): array { $params = [ 'CorpNum' => $this->formatBizNo($corpNum), 'AvailOnly' => $availOnly, ]; return $this->call('bankaccount', 'GetBankAccountEx', $params); } /** * 바로빌 URL 조회 (범용) * * @param string $corpNum 회원사 사업자번호 * @param string $userId 회원사 사용자 ID * @param string $userPwd 회원사 사용자 비밀번호 * @param string $togo 이동할 페이지 코드 */ public function getBarobillUrl(string $corpNum, string $userId, string $userPwd, string $togo = ''): array { $params = [ 'CorpNum' => $this->formatBizNo($corpNum), 'ID' => $userId, 'PWD' => $userPwd, 'TOGO' => $togo, ]; return $this->call('bankaccount', 'GetBaroBillURL', $params); } /** * 현금충전 URL 조회 * * @param string $corpNum 회원사 사업자번호 * @param string $userId 회원사 사용자 ID * @param string $userPwd 회원사 사용자 비밀번호 */ public function getCashChargeUrl(string $corpNum, string $userId, string $userPwd): array { $params = [ 'CorpNum' => $this->formatBizNo($corpNum), 'ID' => $userId, 'PWD' => $userPwd, ]; return $this->call('bankaccount', 'GetCashChargeURL', $params); } /** * 회원사 충전잔액 확인 * * @param string $corpNum 회원사 사업자번호 */ public function getBalanceCostAmount(string $corpNum): array { $params = [ 'CorpNum' => $this->formatBizNo($corpNum), ]; return $this->call('bankaccount', 'GetBalanceCostAmountEx', $params); } /** * 설정 상태 확인 */ public function checkConfiguration(): array { $dbConfig = $this->loadConfigFromDatabase(); return [ 'cert_key_set' => ! empty($this->certKey), 'cert_key_test_set' => ! empty(config('services.barobill.cert_key_test')), 'cert_key_prod_set' => ! empty(config('services.barobill.cert_key_prod')), 'corp_num_set' => ! empty($this->corpNum), 'test_mode' => $this->isTestMode, 'mode_label' => $this->isTestMode ? '테스트' : '운영', 'soap_url' => $this->soapUrls['corpstate'], 'config_source' => $dbConfig ? 'database' : 'env', 'db_config_name' => $dbConfig?->name, ]; } // ======================================== // 카드조회 서비스 (CARD) // ======================================== /** * 카드사 코드 목록 */ public static array $cardCompanyCodes = [ '01' => '비씨카드', '02' => '국민카드', '03' => '외환카드', '04' => '삼성카드', '05' => '현대카드', '06' => '롯데카드', '07' => '신한카드', '08' => '하나카드', '09' => '우리카드', '10' => 'NH농협카드', '11' => '씨티카드', '12' => '광주카드', '13' => '전북카드', '14' => '수협카드', '15' => '제주카드', '16' => '산업카드', ]; /** * 카드 등록 (RegistCardEx) * * @param string $corpNum 회원사 사업자번호 * @param array $cardData 카드 정보 */ public function registCard(string $corpNum, array $cardData): array { $params = [ 'CorpNum' => $this->formatBizNo($corpNum), 'CardCompany' => $cardData['card_company'] ?? '', 'CardType' => $cardData['card_type'] ?? '1', // 1: 법인, 2: 개인 'CardNum' => preg_replace('/[^0-9]/', '', $cardData['card_num'] ?? ''), 'WebId' => $cardData['web_id'] ?? '', 'WebPwd' => $cardData['web_pwd'] ?? '', 'Alias' => $cardData['alias'] ?? '', 'Usage' => $cardData['usage'] ?? '', 'CollectCycle' => $cardData['collect_cycle'] ?? '1', // 1: 1시간, 2: 3시간, 3: 6시간, 4: 12시간, 5: 24시간 ]; return $this->call('card', 'RegistCardEx', $params); } /** * 카드 목록 조회 (GetCardEx2) * * @param string $corpNum 회원사 사업자번호 * @param bool $availOnly 사용 가능한 카드만 조회 */ public function getCards(string $corpNum, bool $availOnly = true): array { $params = [ 'CorpNum' => $this->formatBizNo($corpNum), 'AvailOnly' => $availOnly ? 1 : 0, ]; return $this->call('card', 'GetCardEx2', $params); } /** * 카드 정보 수정 (UpdateCard) * * @param string $corpNum 회원사 사업자번호 * @param array $cardData 카드 정보 */ public function updateCard(string $corpNum, array $cardData): array { $params = [ 'CorpNum' => $this->formatBizNo($corpNum), 'CardNum' => preg_replace('/[^0-9]/', '', $cardData['card_num'] ?? ''), 'WebId' => $cardData['web_id'] ?? '', 'WebPwd' => $cardData['web_pwd'] ?? '', 'Alias' => $cardData['alias'] ?? '', 'Usage' => $cardData['usage'] ?? '', ]; return $this->call('card', 'UpdateCard', $params); } /** * 카드 해지 (StopCard) * * @param string $corpNum 회원사 사업자번호 * @param string $cardNum 카드번호 */ public function stopCard(string $corpNum, string $cardNum): array { $params = [ 'CorpNum' => $this->formatBizNo($corpNum), 'CardNum' => preg_replace('/[^0-9]/', '', $cardNum), ]; return $this->call('card', 'StopCard', $params); } /** * 카드 해지 취소 (CancelStopCard) * * @param string $corpNum 회원사 사업자번호 * @param string $cardNum 카드번호 */ public function cancelStopCard(string $corpNum, string $cardNum): array { $params = [ 'CorpNum' => $this->formatBizNo($corpNum), 'CardNum' => preg_replace('/[^0-9]/', '', $cardNum), ]; return $this->call('card', 'CancelStopCard', $params); } /** * 카드 일별 사용내역 조회 (GetDailyCardLog) * * @param string $corpNum 회원사 사업자번호 * @param string $id 회원사 ID * @param string $cardNum 카드번호 (빈 값이면 전체) * @param string $baseDate 기준일자 (YYYYMMDD) * @param int $countPerPage 페이지당 건수 * @param int $currentPage 현재 페이지 */ public function getDailyCardLog( string $corpNum, string $id, string $cardNum, string $baseDate, int $countPerPage = 20, int $currentPage = 1 ): array { $params = [ 'CorpNum' => $this->formatBizNo($corpNum), 'ID' => $id, 'CardNum' => preg_replace('/[^0-9]/', '', $cardNum), 'BaseDate' => $baseDate, 'CountPerPage' => $countPerPage, 'CurrentPage' => $currentPage, 'OrderDirection' => 'D', // D: 내림차순, A: 오름차순 ]; return $this->call('card', 'GetDailyCardLog', $params); } /** * 카드 월별 사용내역 조회 (GetMonthlyCardLog) * * @param string $corpNum 회원사 사업자번호 * @param string $id 회원사 ID * @param string $cardNum 카드번호 (빈 값이면 전체) * @param string $baseMonth 기준월 (YYYYMM) * @param int $countPerPage 페이지당 건수 * @param int $currentPage 현재 페이지 */ public function getMonthlyCardLog( string $corpNum, string $id, string $cardNum, string $baseMonth, int $countPerPage = 20, int $currentPage = 1 ): array { $params = [ 'CorpNum' => $this->formatBizNo($corpNum), 'ID' => $id, 'CardNum' => preg_replace('/[^0-9]/', '', $cardNum), 'BaseMonth' => $baseMonth, 'CountPerPage' => $countPerPage, 'CurrentPage' => $currentPage, 'OrderDirection' => 'D', ]; return $this->call('card', 'GetMonthlyCardLog', $params); } /** * 카드 기간별 사용내역 조회 (GetPeriodCardLog) * * @param string $corpNum 회원사 사업자번호 * @param string $id 회원사 ID * @param string $cardNum 카드번호 (빈 값이면 전체) * @param string $startDate 시작일 (YYYYMMDD) * @param string $endDate 종료일 (YYYYMMDD) * @param int $countPerPage 페이지당 건수 * @param int $currentPage 현재 페이지 */ public function getPeriodCardLog( string $corpNum, string $id, string $cardNum, string $startDate, string $endDate, int $countPerPage = 20, int $currentPage = 1 ): array { $params = [ 'CorpNum' => $this->formatBizNo($corpNum), 'ID' => $id, 'CardNum' => preg_replace('/[^0-9]/', '', $cardNum), 'StartDate' => $startDate, 'EndDate' => $endDate, 'CountPerPage' => $countPerPage, 'CurrentPage' => $currentPage, 'OrderDirection' => 'D', ]; return $this->call('card', 'GetPeriodCardLog', $params); } /** * 카드 등록 신청 URL 조회 * * @param string $corpNum 회원사 사업자번호 * @param string $userId 회원사 사용자 ID * @param string $userPwd 회원사 사용자 비밀번호 */ public function getCardScrapRequestUrl(string $corpNum, string $userId, string $userPwd): array { $params = [ 'CorpNum' => $this->formatBizNo($corpNum), 'ID' => $userId, 'PWD' => $userPwd, ]; return $this->call('card', 'GetCardScrapRequestURL', $params); } /** * 카드 관리 URL 조회 * * @param string $corpNum 회원사 사업자번호 * @param string $userId 회원사 사용자 ID * @param string $userPwd 회원사 사용자 비밀번호 */ public function getCardManagementUrl(string $corpNum, string $userId, string $userPwd): array { $params = [ 'CorpNum' => $this->formatBizNo($corpNum), 'ID' => $userId, 'PWD' => $userPwd, ]; return $this->call('card', 'GetCardManagementURL', $params); } /** * 카드 사용내역 URL 조회 * * @param string $corpNum 회원사 사업자번호 * @param string $userId 회원사 사용자 ID * @param string $userPwd 회원사 사용자 비밀번호 */ public function getCardLogUrl(string $corpNum, string $userId, string $userPwd): array { $params = [ 'CorpNum' => $this->formatBizNo($corpNum), 'ID' => $userId, 'PWD' => $userPwd, ]; return $this->call('card', 'GetCardLogURL', $params); } // ======================================== // 계좌조회 확장 (BANKACCOUNT) // ======================================== /** * 은행 코드 목록 */ public static array $bankCodes = [ '02' => '산업은행', '03' => '기업은행', '04' => '국민은행', '05' => '외환은행', '06' => '국민은행(구주택)', '07' => '수협은행', '08' => '수출입은행', '11' => 'NH농협은행', '12' => '지역농축협', '20' => '우리은행', '21' => '신한은행(구조흥)', '22' => '상업은행(구)', '23' => '제일은행(SC)', '25' => '하나은행(구 서울)', '26' => '신한은행', '27' => '한국씨티은행', '31' => '대구은행', '32' => '부산은행', '34' => '광주은행', '35' => '제주은행', '37' => '전북은행', '38' => '강원은행(구)', '39' => '경남은행', '41' => '비씨카드', '45' => '새마을금고연합', '48' => '신협', '50' => '상호저축은행', '53' => '한국씨티은행(구)', '54' => 'HSBC은행', '55' => '도이치은행', '56' => 'ABN-AMRO', '57' => 'JP모간', '59' => '미쓰비시도쿄은행', '60' => 'BOA', '64' => '산림조합중앙회', '71' => '우체국', '81' => 'KEB하나은행', '88' => '신한은행(통합)', '89' => '케이뱅크', '90' => '카카오뱅크', '92' => '토스뱅크', ]; /** * 계좌 일별 입출금내역 조회 * * @param string $corpNum 회원사 사업자번호 * @param string $id 회원사 ID * @param string $bankAccountNum 계좌번호 (빈 값이면 전체) * @param string $baseDate 기준일자 (YYYYMMDD) * @param string $transDirection I: 입금, O: 출금, '' 전체 * @param int $countPerPage 페이지당 건수 * @param int $currentPage 현재 페이지 */ public function getDailyBankAccountTransLog( string $corpNum, string $id, string $bankAccountNum, string $baseDate, string $transDirection = '', int $countPerPage = 20, int $currentPage = 1 ): array { $params = [ 'CorpNum' => $this->formatBizNo($corpNum), 'ID' => $id, 'BankAccountNum' => $bankAccountNum, 'BaseDate' => $baseDate, 'TransDirection' => $transDirection, 'CountPerPage' => $countPerPage, 'CurrentPage' => $currentPage, 'OrderDirection' => 'D', ]; return $this->call('bankaccount', 'GetDailyBankAccountTransLog', $params); } /** * 계좌 월별 입출금내역 조회 */ public function getMonthlyBankAccountTransLog( string $corpNum, string $id, string $bankAccountNum, string $baseMonth, string $transDirection = '', int $countPerPage = 20, int $currentPage = 1 ): array { $params = [ 'CorpNum' => $this->formatBizNo($corpNum), 'ID' => $id, 'BankAccountNum' => $bankAccountNum, 'BaseMonth' => $baseMonth, 'TransDirection' => $transDirection, 'CountPerPage' => $countPerPage, 'CurrentPage' => $currentPage, 'OrderDirection' => 'D', ]; return $this->call('bankaccount', 'GetMonthlyBankAccountTransLog', $params); } /** * 계좌 기간별 입출금내역 조회 */ public function getPeriodBankAccountTransLog( string $corpNum, string $id, string $bankAccountNum, string $startDate, string $endDate, string $transDirection = '', int $countPerPage = 20, int $currentPage = 1 ): array { $params = [ 'CorpNum' => $this->formatBizNo($corpNum), 'ID' => $id, 'BankAccountNum' => $bankAccountNum, 'StartDate' => $startDate, 'EndDate' => $endDate, 'TransDirection' => $transDirection, 'CountPerPage' => $countPerPage, 'CurrentPage' => $currentPage, 'OrderDirection' => 'D', ]; return $this->call('bankaccount', 'GetPeriodBankAccountTransLog', $params); } // ======================================== // 전자세금계산서 서비스 (TI) // ======================================== /** * 세금계산서 발행 URL 조회 * * @param string $corpNum 회원사 사업자번호 * @param string $userId 회원사 사용자 ID * @param string $userPwd 회원사 사용자 비밀번호 */ public function getTaxInvoiceIssueUrl(string $corpNum, string $userId, string $userPwd): array { $params = [ 'CorpNum' => $this->formatBizNo($corpNum), 'ID' => $userId, 'PWD' => $userPwd, ]; return $this->call('ti', 'GetTaxInvoiceIssueURL', $params); } /** * 세금계산서 목록 URL 조회 * * @param string $corpNum 회원사 사업자번호 * @param string $userId 회원사 사용자 ID * @param string $userPwd 회원사 사용자 비밀번호 */ public function getTaxInvoiceListUrl(string $corpNum, string $userId, string $userPwd): array { $params = [ 'CorpNum' => $this->formatBizNo($corpNum), 'ID' => $userId, 'PWD' => $userPwd, ]; return $this->call('ti', 'GetTaxInvoiceListURL', $params); } /** * 매입/매출 관리 URL 조회 * * @param string $corpNum 회원사 사업자번호 * @param string $userId 회원사 사용자 ID * @param string $userPwd 회원사 사용자 비밀번호 */ public function getHomeTaxUrl(string $corpNum, string $userId, string $userPwd): array { $params = [ 'CorpNum' => $this->formatBizNo($corpNum), 'ID' => $userId, 'PWD' => $userPwd, ]; return $this->call('ti', 'GetHomeTaxURL', $params); } // ======================================== // 카카오톡 서비스 (KAKAOTALK) // ======================================== /** * KAKAOTALK SOAP 클라이언트 가져오기 */ protected function getKakaotalkClient(): ?SoapClient { if ($this->kakaotalkClient === null) { try { $this->kakaotalkClient = new SoapClient($this->soapUrls['kakaotalk'], [ 'trace' => true, 'encoding' => 'UTF-8', 'exceptions' => true, 'connection_timeout' => 30, 'cache_wsdl' => WSDL_CACHE_NONE, ]); } catch (Throwable $e) { Log::error('바로빌 KAKAOTALK SOAP 클라이언트 생성 실패', [ 'error' => $e->getMessage(), 'url' => $this->soapUrls['kakaotalk'], ]); return null; } } return $this->kakaotalkClient; } // --- 채널 관리 --- /** * 카카오톡 채널 목록 조회 * * @param string $corpNum 회원사 사업자번호 */ public function getKakaotalkChannels(string $corpNum): array { $params = [ 'CorpNum' => $this->formatBizNo($corpNum), ]; return $this->call('kakaotalk', 'GetKakaotalkChannels', $params); } /** * 카카오톡 채널 관리 URL 조회 * * @param string $corpNum 회원사 사업자번호 * @param string $id 회원사 사용자 ID */ public function getKakaotalkChannelManagementUrl(string $corpNum, string $id): array { $params = [ 'CorpNum' => $this->formatBizNo($corpNum), 'ID' => $id, ]; return $this->call('kakaotalk', 'GetKakaotalkChannelManagementURL', $params); } // --- 템플릿 관리 --- /** * 카카오톡 템플릿 목록 조회 * * @param string $corpNum 회원사 사업자번호 * @param string $channelId 카카오톡 채널 ID */ public function getKakaotalkTemplates(string $corpNum, string $channelId): array { $params = [ 'CorpNum' => $this->formatBizNo($corpNum), 'ChannelId' => $channelId, ]; return $this->call('kakaotalk', 'GetKakaotalkTemplates', $params); } /** * 카카오톡 템플릿 관리 URL 조회 * * @param string $corpNum 회원사 사업자번호 * @param string $id 회원사 사용자 ID */ public function getKakaotalkTemplateManagementUrl(string $corpNum, string $id): array { $params = [ 'CorpNum' => $this->formatBizNo($corpNum), 'ID' => $id, ]; return $this->call('kakaotalk', 'GetKakaotalkTemplateManagementURL', $params); } // --- 알림톡 발송 --- /** * 알림톡 단건 발송 * * @param string $corpNum 회원사 사업자번호 * @param string $senderId 바로빌 계정 ID * @param string $yellowId 카카오톡 채널 ID (예: @codebridge-x) * @param string $templateName 템플릿 이름 * @param string $receiverName 수신자 이름 * @param string $receiverNum 수신자 전화번호 * @param string $title 알림톡 제목 * @param string $message 알림톡 본문 * @param string $smsMessage SMS 대체발송 메시지 (빈 값이면 대체발송 안 함) * @param string $smsSubject SMS 대체발송 제목 * @param string $smsSenderNum SMS 발신번호 * @param string $reserveDT 예약일시 (YYYYMMDDHHmmss, 빈 값이면 즉시발송) */ public function sendATKakaotalk( string $corpNum, string $senderId, string $yellowId, string $templateName, string $receiverName, string $receiverNum, string $title, string $message, string $smsMessage = '', string $smsSubject = '', string $smsSenderNum = '', string $reserveDT = '' ): array { $params = [ 'CorpNum' => $this->formatBizNo($corpNum), 'SenderID' => $senderId, 'YellowId' => $yellowId, 'TemplateName' => $templateName, 'SendDT' => $reserveDT, 'SmsReply' => (empty($smsMessage) || empty($smsSenderNum)) ? 'N' : 'S', 'SmsSenderNum' => $smsSenderNum, 'KakaotalkMessage' => [ 'ReceiverName' => $receiverName, 'ReceiverNum' => $receiverNum, 'Title' => $title, 'Message' => $message, 'SmsMessage' => $smsMessage, 'SmsSubject' => $smsSubject, ], ]; return $this->call('kakaotalk', 'SendATKakaotalk', $params); } /** * 알림톡 단건 발송 (버튼 포함) * * @param string $corpNum 회원사 사업자번호 * @param string $senderId 바로빌 계정 ID * @param string $yellowId 카카오톡 채널 ID (예: @codebridge-x) * @param string $templateName 템플릿 이름 * @param string $receiverName 수신자 이름 * @param string $receiverNum 수신자 전화번호 * @param string $title 알림톡 제목 * @param string $message 알림톡 본문 * @param array $buttons 버튼 배열 [{Name, ButtonType, Url1, Url2}] * @param string $smsMessage SMS 대체발송 메시지 * @param string $smsSubject SMS 대체발송 제목 * @param string $smsSenderNum SMS 발신번호 * @param string $reserveDT 예약일시 */ public function sendATKakaotalkEx( string $corpNum, string $senderId, string $yellowId, string $templateName, string $receiverName, string $receiverNum, string $title, string $message, array $buttons = [], string $smsMessage = '', string $smsSubject = '', string $smsSenderNum = '', string $reserveDT = '' ): array { $kakaotalkMessage = [ 'ReceiverName' => $receiverName, 'ReceiverNum' => $receiverNum, 'Title' => $title, 'Message' => $message, 'SmsMessage' => $smsMessage, 'SmsSubject' => $smsSubject, ]; if (! empty($buttons)) { $kakaotalkMessage['Buttons'] = ['KakaotalkButton' => $buttons]; } $params = [ 'CorpNum' => $this->formatBizNo($corpNum), 'SenderID' => $senderId, 'YellowId' => $yellowId, 'TemplateName' => $templateName, 'SendDT' => $reserveDT, 'SmsReply' => (empty($smsMessage) || empty($smsSenderNum)) ? 'N' : 'S', 'SmsSenderNum' => $smsSenderNum, 'KakaotalkMessage' => $kakaotalkMessage, ]; return $this->call('kakaotalk', 'SendATKakaotalkEx', $params); } /** * 알림톡 대량 발송 * * @param string $corpNum 회원사 사업자번호 * @param string $senderId 바로빌 계정 ID * @param string $yellowId 카카오톡 채널 ID (예: @codebridge-x) * @param string $templateName 템플릿 이름 * @param array $messages 메시지 배열 [{ReceiverName, ReceiverNum, Title, Message, SmsMessage, SmsSubject}] * @param string $reserveDT 예약일시 */ public function sendATKakaotalks( string $corpNum, string $senderId, string $yellowId, string $templateName, array $messages, string $reserveDT = '' ): array { $params = [ 'CorpNum' => $this->formatBizNo($corpNum), 'SenderID' => $senderId, 'YellowId' => $yellowId, 'TemplateName' => $templateName, 'SendDT' => $reserveDT, 'SmsReply' => 'N', 'SmsSenderNum' => '', 'KakaotalkMessages' => ['KakaotalkATMessage' => $messages], ]; return $this->call('kakaotalk', 'SendATKakaotalks', $params); } // --- 친구톡 발송 --- /** * 친구톡 텍스트 단건 발송 * * @param string $corpNum 회원사 사업자번호 * @param string $senderId 발신프로필 키 * @param string $channelId 채널 ID * @param string $receiverName 수신자 이름 * @param string $receiverNum 수신자 전화번호 * @param string $message 메시지 내용 * @param array $buttons 버튼 배열 * @param string $smsMessage SMS 대체발송 메시지 * @param string $smsSubject SMS 대체발송 제목 * @param bool $adYn 광고 여부 * @param string $reserveDT 예약일시 */ public function sendFTKakaotalk( string $corpNum, string $senderId, string $channelId, string $receiverName, string $receiverNum, string $message, array $buttons = [], string $smsMessage = '', string $smsSubject = '', bool $adYn = false, string $reserveDT = '' ): array { $params = [ 'CorpNum' => $this->formatBizNo($corpNum), 'SenderID' => $senderId, 'ChannelId' => $channelId, 'ReceiverName' => $receiverName, 'ReceiverNum' => $receiverNum, 'Message' => $message, 'Buttons' => $buttons, 'SmsMessage' => $smsMessage, 'SmsSubject' => $smsSubject, 'AdYn' => $adYn ? 'Y' : 'N', 'ReserveDT' => $reserveDT, ]; return $this->call('kakaotalk', 'SendFTKakaotalk', $params); } /** * 친구톡 대량 발송 * * @param string $corpNum 회원사 사업자번호 * @param string $senderId 발신프로필 키 * @param string $channelId 채널 ID * @param array $messages 메시지 배열 * @param bool $adYn 광고 여부 * @param string $reserveDT 예약일시 */ public function sendFTKakaotalks( string $corpNum, string $senderId, string $channelId, array $messages, bool $adYn = false, string $reserveDT = '' ): array { $params = [ 'CorpNum' => $this->formatBizNo($corpNum), 'SenderID' => $senderId, 'ChannelId' => $channelId, 'Messages' => $messages, 'AdYn' => $adYn ? 'Y' : 'N', 'ReserveDT' => $reserveDT, ]; return $this->call('kakaotalk', 'SendFTKakaotalks', $params); } /** * 친구톡 이미지 발송 * * @param string $corpNum 회원사 사업자번호 * @param string $senderId 발신프로필 키 * @param string $channelId 채널 ID * @param string $receiverName 수신자 이름 * @param string $receiverNum 수신자 전화번호 * @param string $message 메시지 내용 * @param string $imageUrl 이미지 URL * @param array $buttons 버튼 배열 * @param string $smsMessage SMS 대체발송 메시지 * @param string $smsSubject SMS 대체발송 제목 * @param bool $adYn 광고 여부 * @param string $reserveDT 예약일시 */ public function sendFIKakaotalk( string $corpNum, string $senderId, string $channelId, string $receiverName, string $receiverNum, string $message, string $imageUrl, array $buttons = [], string $smsMessage = '', string $smsSubject = '', bool $adYn = false, string $reserveDT = '' ): array { $params = [ 'CorpNum' => $this->formatBizNo($corpNum), 'SenderID' => $senderId, 'ChannelId' => $channelId, 'ReceiverName' => $receiverName, 'ReceiverNum' => $receiverNum, 'Message' => $message, 'ImageURL' => $imageUrl, 'Buttons' => $buttons, 'SmsMessage' => $smsMessage, 'SmsSubject' => $smsSubject, 'AdYn' => $adYn ? 'Y' : 'N', 'ReserveDT' => $reserveDT, ]; return $this->call('kakaotalk', 'SendFIKakaotalk', $params); } /** * 친구톡 와이드 이미지 발송 * * @param string $corpNum 회원사 사업자번호 * @param string $senderId 발신프로필 키 * @param string $channelId 채널 ID * @param string $receiverName 수신자 이름 * @param string $receiverNum 수신자 전화번호 * @param string $message 메시지 내용 * @param string $imageUrl 와이드 이미지 URL * @param array $buttons 버튼 배열 * @param string $smsMessage SMS 대체발송 메시지 * @param string $smsSubject SMS 대체발송 제목 * @param bool $adYn 광고 여부 * @param string $reserveDT 예약일시 */ public function sendFWKakaotalk( string $corpNum, string $senderId, string $channelId, string $receiverName, string $receiverNum, string $message, string $imageUrl, array $buttons = [], string $smsMessage = '', string $smsSubject = '', bool $adYn = false, string $reserveDT = '' ): array { $params = [ 'CorpNum' => $this->formatBizNo($corpNum), 'SenderID' => $senderId, 'ChannelId' => $channelId, 'ReceiverName' => $receiverName, 'ReceiverNum' => $receiverNum, 'Message' => $message, 'ImageURL' => $imageUrl, 'Buttons' => $buttons, 'SmsMessage' => $smsMessage, 'SmsSubject' => $smsSubject, 'AdYn' => $adYn ? 'Y' : 'N', 'ReserveDT' => $reserveDT, ]; return $this->call('kakaotalk', 'SendFWKakaotalk', $params); } // --- 전송 조회/관리 --- /** * 전송 결과 조회 (단건) * * @param string $corpNum 회원사 사업자번호 * @param string $sendKey 전송키 */ public function getSendKakaotalk(string $corpNum, string $sendKey): array { $params = [ 'CorpNum' => $this->formatBizNo($corpNum), 'SendKey' => $sendKey, ]; return $this->call('kakaotalk', 'GetSendKakaotalk', $params); } /** * 전송 결과 조회 (다건) * * @param string $corpNum 회원사 사업자번호 * @param array $sendKeyList 전송키 배열 */ public function getSendKakaotalks(string $corpNum, array $sendKeyList): array { $params = [ 'CorpNum' => $this->formatBizNo($corpNum), 'SendKeyList' => $sendKeyList, ]; return $this->call('kakaotalk', 'GetSendKakaotalks', $params); } /** * 예약 전송 취소 * * @param string $corpNum 회원사 사업자번호 * @param string $sendKey 전송키 */ public function cancelReservedKakaotalk(string $corpNum, string $sendKey): array { $params = [ 'CorpNum' => $this->formatBizNo($corpNum), 'SendKey' => $sendKey, ]; return $this->call('kakaotalk', 'CancelReservedKakaotalk', $params); } // ======================================== // 문자 서비스 (SMS) // ======================================== /** * SMS SOAP 클라이언트 가져오기 */ protected function getSmsClient(): ?SoapClient { if ($this->smsClient === null) { try { $this->smsClient = new SoapClient($this->soapUrls['sms'], [ 'trace' => true, 'encoding' => 'UTF-8', 'exceptions' => true, 'connection_timeout' => 30, 'cache_wsdl' => WSDL_CACHE_NONE, ]); } catch (Throwable $e) { Log::error('바로빌 SMS SOAP 클라이언트 생성 실패', [ 'error' => $e->getMessage(), 'url' => $this->soapUrls['sms'], ]); return null; } } return $this->smsClient; } /** * SMS 단문 발송 * * @param string $corpNum 회원사 사업자번호 * @param string $senderId 바로빌 계정 ID * @param string $fromNumber 발신번호 * @param string $toName 수신자명 * @param string $toNumber 수신 전화번호 * @param string $contents 메시지 내용 (90바이트 이내) * @param string $sendDT 예약일시 (빈값 = 즉시발송) * @param string $refKey 참조키 */ public function sendSMSMessage( string $corpNum, string $senderId, string $fromNumber, string $toName, string $toNumber, string $contents, string $sendDT = '', string $refKey = '' ): array { $params = [ 'CorpNum' => $this->formatBizNo($corpNum), 'SenderID' => $senderId, 'FromNumber' => $fromNumber, 'ToName' => $toName, 'ToNumber' => $toNumber, 'Contents' => $contents, 'SendDT' => $sendDT, 'RefKey' => $refKey, ]; return $this->call('sms', 'SendSMSMessage', $params); } /** * 발신번호 등록 여부 확인 * * @param string $corpNum 회원사 사업자번호 * @param string $fromNumber 확인할 발신번호 */ public function checkSMSFromNumber(string $corpNum, string $fromNumber): array { $params = [ 'CorpNum' => $this->formatBizNo($corpNum), 'FromNumber' => $fromNumber, ]; return $this->call('sms', 'CheckSMSFromNumber', $params); } /** * 등록된 발신번호 목록 조회 * * @param string $corpNum 회원사 사업자번호 */ public function getSMSFromNumbers(string $corpNum): array { $params = [ 'CorpNum' => $this->formatBizNo($corpNum), ]; return $this->call('sms', 'GetSMSFromNumbers', $params); } /** * SMS 전송 상태 조회 * * @param string $corpNum 회원사 사업자번호 * @param string $sendKey 전송키 */ public function getSMSSendState(string $corpNum, string $sendKey): array { $params = [ 'CorpNum' => $this->formatBizNo($corpNum), 'SendKey' => $sendKey, ]; return $this->call('sms', 'GetSMSSendState', $params); } }