Files
sam-api/app/Services/Barobill/BarobillSoapService.php
김보곤 b576fe97e8 feat: [barobill] 바로빌 연동 관리 API 7개 엔드포인트 구현
- SOAP 기반 BarobillSoapService 생성 (MNG 코드 포팅)
- BarobillMember, BarobillConfig 모델 생성
- BarobillController 7개 메서드 (login, signup, status, URL 조회)
- FormRequest 검증 클래스 3개 생성
- 라우트 등록 (POST /barobill/login, /signup, GET /status 등)
- i18n 메시지 키 추가 (ko/en)
- config/services.php에 barobill 설정 추가
2026-02-21 17:19:18 +09:00

605 lines
20 KiB
PHP

<?php
namespace App\Services\Barobill;
use App\Models\Tenants\BarobillConfig;
use App\Models\Tenants\BarobillMember;
use App\Services\Service;
use Illuminate\Support\Facades\Log;
use SoapClient;
use SoapFault;
use Symfony\Component\HttpKernel\Exception\BadRequestHttpException;
use Throwable;
class BarobillSoapService extends Service
{
protected ?SoapClient $corpStateClient = null;
protected ?SoapClient $bankAccountClient = null;
protected ?SoapClient $cardClient = null;
protected string $certKey;
protected string $corpNum;
protected bool $isTestMode;
protected array $soapUrls;
protected array $errorMessages = [
-11101 => '사업자번호가 설정되지 않았거나 유효하지 않습니다.',
-11102 => 'CERTKEY가 유효하지 않습니다.',
-11103 => '인증서가 만료되었거나 유효하지 않습니다.',
-11104 => '해당 사업자가 등록되어 있지 않습니다.',
-11105 => '이미 등록된 사업자입니다.',
-11106 => '아이디가 이미 존재합니다.',
-11201 => '필수 파라미터가 누락되었습니다.',
-26001 => '공동인증서가 등록되어 있지 않습니다.',
-32000 => '알 수 없는 오류가 발생했습니다.',
-32001 => '사업자번호가 유효하지 않습니다.',
-32002 => '아이디가 유효하지 않습니다.',
-32003 => '비밀번호가 유효하지 않습니다.',
-32004 => '상호명이 유효하지 않습니다.',
-32005 => '대표자명이 유효하지 않습니다.',
-32006 => '이메일 형식이 유효하지 않습니다.',
-32010 => '이미 등록된 사업자번호입니다.',
-32011 => '이미 등록된 아이디입니다.',
-32012 => '이미 등록된 아이디입니다. 다른 아이디를 사용해주세요.',
-32013 => '비밀번호 형식이 유효하지 않습니다. (영문/숫자/특수문자 조합 8자리 이상)',
-32014 => '연락처 형식이 유효하지 않습니다.',
-32020 => '파트너 사업자번호가 유효하지 않습니다.',
-32021 => '파트너 인증키(CERTKEY)가 유효하지 않습니다.',
-99999 => '서버 내부 오류가 발생했습니다.',
];
public function __construct()
{
$this->isTestMode = config('services.barobill.test_mode', true);
$this->initializeConfig();
}
public function switchServerMode(bool $isTestMode): self
{
if ($this->isTestMode !== $isTestMode) {
$this->isTestMode = $isTestMode;
$this->corpStateClient = null;
$this->bankAccountClient = null;
$this->cardClient = null;
$this->initializeConfig();
}
return $this;
}
protected function initializeConfig(): void
{
$dbConfig = $this->loadConfigFromDatabase();
if ($dbConfig) {
$this->certKey = $dbConfig->cert_key;
$this->corpNum = $dbConfig->corp_num ?? '';
$this->soapUrls = $this->buildSoapUrls($dbConfig->base_url);
} else {
$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 = $this->buildSoapUrls($baseUrl);
}
}
protected function loadConfigFromDatabase(): ?BarobillConfig
{
try {
return BarobillConfig::getActive($this->isTestMode);
} catch (\Exception $e) {
Log::warning('바로빌 DB 설정 로드 실패', ['error' => $e->getMessage()]);
return null;
}
}
protected function buildSoapUrls(string $baseUrl): array
{
$baseUrl = rtrim($baseUrl, '/');
return [
'corpstate' => $baseUrl.'/CORPSTATE.asmx?WSDL',
'bankaccount' => $baseUrl.'/BANKACCOUNT.asmx?WSDL',
'card' => $baseUrl.'/CARD.asmx?WSDL',
];
}
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;
}
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;
}
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;
}
protected function call(string $service, string $method, array $params = []): array
{
$client = match ($service) {
'corpstate' => $this->getCorpStateClient(),
'bankaccount' => $this->getBankAccountClient(),
'card' => $this->getCardClient(),
default => null,
};
if (! $client) {
return [
'success' => false,
'error' => "SOAP 클라이언트가 초기화되지 않았습니다. ({$service})",
'error_code' => -1,
];
}
if (empty($this->certKey)) {
return [
'success' => false,
'error' => 'CERTKEY가 설정되지 않았습니다.',
'error_code' => -2,
];
}
if (! isset($params['CERTKEY'])) {
$params['CERTKEY'] = $this->certKey;
}
try {
Log::info('바로빌 SOAP 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('바로빌 SOAP API 오류', [
'method' => $method,
'error_code' => $resultData,
'error_message' => $errorMessage,
]);
return [
'success' => false,
'error' => $errorMessage,
'error_code' => $resultData,
];
}
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('바로빌 SOAP API 호출 오류', [
'method' => $method,
'error' => $e->getMessage(),
]);
return [
'success' => false,
'error' => 'API 호출 오류: '.$e->getMessage(),
'error_code' => -999,
];
}
}
protected function formatBizNo(string $bizNo): string
{
return preg_replace('/[^0-9]/', '', $bizNo);
}
// =========================================================================
// 회원사 조회
// =========================================================================
protected function getMember(): BarobillMember
{
$tenantId = $this->tenantId();
$member = BarobillMember::where('tenant_id', $tenantId)->first();
if (! $member) {
throw new BadRequestHttpException(__('error.barobill.member_not_found'));
}
$this->switchServerMode($member->isTestMode());
return $member;
}
// =========================================================================
// SOAP 비즈니스 메서드
// =========================================================================
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',
'TEL' => $data['tel'] ?? '',
'HP' => $data['manager_hp'] ?? '',
'Email' => $data['manager_email'] ?? '',
];
return $this->call('corpstate', 'RegistCorp', $params);
}
public function getCorpState(string $corpNum): array
{
$params = [
'CorpNum' => $this->formatBizNo($corpNum),
];
return $this->call('corpstate', 'GetCorpState', $params);
}
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);
}
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);
}
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);
}
public function getBankAccounts(string $corpNum, bool $availOnly = true): array
{
$params = [
'CorpNum' => $this->formatBizNo($corpNum),
'AvailOnly' => $availOnly,
];
return $this->call('bankaccount', 'GetBankAccountEx', $params);
}
public function getCards(string $corpNum, bool $availOnly = true): array
{
$params = [
'CorpNum' => $this->formatBizNo($corpNum),
'AvailOnly' => $availOnly ? 1 : 0,
];
return $this->call('card', 'GetCardEx2', $params);
}
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);
}
// =========================================================================
// 컨트롤러 비즈니스 메서드
// =========================================================================
public function registerLogin(array $data): array
{
$tenantId = $this->tenantId();
$barobillId = $data['barobill_id'];
$password = $data['password'];
$member = BarobillMember::where('tenant_id', $tenantId)->first();
if ($member) {
$this->switchServerMode($member->isTestMode());
}
$result = $this->getCorpState($member?->biz_no ?? config('services.barobill.corp_num'));
if (! $result['success']) {
throw new BadRequestHttpException($result['error']);
}
$member = BarobillMember::updateOrCreate(
['tenant_id' => $tenantId],
[
'barobill_id' => $barobillId,
'barobill_pwd' => $password,
'status' => 'active',
]
);
return [
'id' => $member->id,
'barobill_id' => $member->barobill_id,
'status' => $member->status,
];
}
public function registerSignup(array $data): array
{
$tenantId = $this->tenantId();
$soapResult = $this->registCorp([
'biz_no' => $data['business_number'],
'corp_name' => $data['company_name'],
'ceo_name' => $data['ceo_name'],
'biz_type' => $data['business_type'] ?? '',
'biz_class' => $data['business_category'] ?? '',
'addr' => $data['address'] ?? '',
'barobill_id' => $data['barobill_id'],
'barobill_pwd' => $data['password'],
'manager_name' => $data['manager_name'] ?? '',
'manager_hp' => $data['manager_phone'] ?? '',
'manager_email' => $data['manager_email'] ?? '',
]);
if (! $soapResult['success']) {
throw new BadRequestHttpException($soapResult['error']);
}
$member = BarobillMember::updateOrCreate(
['tenant_id' => $tenantId],
[
'biz_no' => $this->formatBizNo($data['business_number']),
'corp_name' => $data['company_name'],
'ceo_name' => $data['ceo_name'],
'biz_type' => $data['business_type'] ?? '',
'biz_class' => $data['business_category'] ?? '',
'addr' => $data['address'] ?? '',
'barobill_id' => $data['barobill_id'],
'barobill_pwd' => $data['password'],
'manager_name' => $data['manager_name'] ?? '',
'manager_hp' => $data['manager_phone'] ?? '',
'manager_email' => $data['manager_email'] ?? '',
'status' => 'active',
'server_mode' => $this->isTestMode ? 'test' : 'production',
]
);
return [
'id' => $member->id,
'barobill_id' => $member->barobill_id,
'biz_no' => $member->formatted_biz_no,
'status' => $member->status,
];
}
public function getBankServiceRedirectUrl(array $params): array
{
$member = $this->getMember();
$result = $this->getBankAccountLogUrl(
$member->biz_no,
$member->barobill_id,
$member->barobill_pwd
);
if (! $result['success']) {
throw new BadRequestHttpException($result['error']);
}
return ['url' => $result['data']];
}
public function getIntegrationStatus(): array
{
$member = $this->getMember();
$bankResult = $this->getBankAccounts($member->biz_no);
$cardResult = $this->getCards($member->biz_no);
$bankCount = 0;
if ($bankResult['success'] && isset($bankResult['data'])) {
$data = $bankResult['data'];
if (is_object($data) && isset($data->BankAccountInfo)) {
$info = $data->BankAccountInfo;
$bankCount = is_array($info) ? count($info) : 1;
} elseif (is_array($data)) {
$bankCount = count($data);
}
}
$cardCount = 0;
if ($cardResult['success'] && isset($cardResult['data'])) {
$data = $cardResult['data'];
if (is_object($data) && isset($data->CardInfo)) {
$info = $data->CardInfo;
$cardCount = is_array($info) ? count($info) : 1;
} elseif (is_array($data)) {
$cardCount = count($data);
}
}
return [
'bank_account_count' => $bankCount,
'card_count' => $cardCount,
'member' => [
'barobill_id' => $member->barobill_id,
'biz_no' => $member->formatted_biz_no,
'status' => $member->status,
'server_mode' => $member->server_mode,
],
];
}
public function getAccountLinkRedirectUrl(): array
{
$member = $this->getMember();
$result = $this->getBankAccountScrapRequestUrl(
$member->biz_no,
$member->barobill_id,
$member->barobill_pwd
);
if (! $result['success']) {
throw new BadRequestHttpException($result['error']);
}
return ['url' => $result['data']];
}
public function getCardLinkRedirectUrl(): array
{
$member = $this->getMember();
$result = $this->getCardScrapRequestUrl(
$member->biz_no,
$member->barobill_id,
$member->barobill_pwd
);
if (! $result['success']) {
throw new BadRequestHttpException($result['error']);
}
return ['url' => $result['data']];
}
public function getCertificateRedirectUrl(): array
{
$member = $this->getMember();
$result = $this->getCertificateRegistUrl(
$member->biz_no,
$member->barobill_id,
$member->barobill_pwd
);
if (! $result['success']) {
throw new BadRequestHttpException($result['error']);
}
return ['url' => $result['data']];
}
}