- BarobillSoapService: PHP SoapClient 기반 SOAP 래퍼 (회원/계좌/카드/인증서) - BarobillBankSyncService: 은행 거래내역 SOAP 조회 → DB 캐시 동기화 - BarobillCardSyncService: 카드 거래내역 SOAP 조회 → DB 캐시 동기화 - HometaxSyncService: 홈택스 세금계산서 upsert 동기화 - BarobillSyncController: 동기화/회원/인증서/잔액 API 11개 엔드포인트 - SyncBarobillDataJob: 매일 06:00/06:30 자동 동기화 스케줄러 - BarobillController.status() 보강: 실제 계좌/카드 수 표시
294 lines
9.4 KiB
PHP
294 lines
9.4 KiB
PHP
<?php
|
|
|
|
namespace App\Services\Barobill;
|
|
|
|
use App\Models\Barobill\BarobillBankSyncStatus;
|
|
use App\Models\Barobill\BarobillMember;
|
|
use App\Services\Service;
|
|
use Carbon\Carbon;
|
|
use Illuminate\Support\Facades\DB;
|
|
use Illuminate\Support\Facades\Log;
|
|
|
|
/**
|
|
* 바로빌 은행 거래내역 동기화 서비스 (API 독립 구현)
|
|
*
|
|
* MNG의 BarobillBankSyncService 패턴을 참고하여 독립 작성.
|
|
* SOAP API를 호출하여 은행 거래내역을 DB에 캐시/동기화한다.
|
|
*/
|
|
class BarobillBankSyncService extends Service
|
|
{
|
|
public function __construct(
|
|
protected BarobillSoapService $soapService
|
|
) {}
|
|
|
|
/**
|
|
* 지정 기간의 거래내역이 최신인지 확인하고, 필요 시 바로빌 API에서 동기화
|
|
*/
|
|
public function syncIfNeeded(int $tenantId, string $startDateYmd, string $endDateYmd): array
|
|
{
|
|
$member = BarobillMember::withoutGlobalScopes()
|
|
->where('tenant_id', $tenantId)
|
|
->first();
|
|
|
|
if (! $member || empty($member->barobill_id)) {
|
|
return ['success' => false, 'error' => '바로빌 회원 정보 없음'];
|
|
}
|
|
|
|
$this->soapService->initForMember($member);
|
|
|
|
$accounts = $this->getRegisteredAccounts($member);
|
|
if (empty($accounts)) {
|
|
return ['success' => true, 'message' => '등록된 계좌 없음', 'synced' => 0];
|
|
}
|
|
|
|
$currentYearMonth = Carbon::now()->format('Ym');
|
|
$chunks = $this->splitDateRangeMonthly($startDateYmd, $endDateYmd);
|
|
$totalSynced = 0;
|
|
|
|
foreach ($accounts as $acc) {
|
|
$accNum = $acc['bankAccountNum'];
|
|
|
|
foreach ($chunks as $chunk) {
|
|
$yearMonth = substr($chunk['start'], 0, 6);
|
|
$isCurrentMonth = ($yearMonth === $currentYearMonth);
|
|
|
|
$syncStatus = BarobillBankSyncStatus::withoutGlobalScopes()
|
|
->where('tenant_id', $tenantId)
|
|
->where('bank_account_num', $accNum)
|
|
->where('synced_year_month', $yearMonth)
|
|
->first();
|
|
|
|
if ($syncStatus) {
|
|
if (! $isCurrentMonth) {
|
|
continue;
|
|
}
|
|
if ($syncStatus->synced_at && $syncStatus->synced_at->diffInMinutes(now()) < 10) {
|
|
continue;
|
|
}
|
|
}
|
|
|
|
$count = $this->fetchAndCache(
|
|
$tenantId,
|
|
$member->barobill_id,
|
|
$accNum,
|
|
$acc['bankName'],
|
|
$acc['bankCode'],
|
|
$chunk['start'],
|
|
$chunk['end'],
|
|
$yearMonth
|
|
);
|
|
$totalSynced += $count;
|
|
}
|
|
}
|
|
|
|
return [
|
|
'success' => true,
|
|
'synced' => $totalSynced,
|
|
'accounts' => count($accounts),
|
|
];
|
|
}
|
|
|
|
/**
|
|
* 바로빌 등록 계좌 목록 조회 (SOAP)
|
|
*/
|
|
public function getRegisteredAccounts(BarobillMember $member): array
|
|
{
|
|
$result = $this->soapService->getBankAccounts($member->biz_no, false);
|
|
if (! $result['success']) {
|
|
return [];
|
|
}
|
|
|
|
$data = $result['data'];
|
|
$accountList = [];
|
|
if (isset($data->BankAccount)) {
|
|
$accountList = is_array($data->BankAccount) ? $data->BankAccount : [$data->BankAccount];
|
|
} elseif (isset($data->BankAccountEx)) {
|
|
$accountList = is_array($data->BankAccountEx) ? $data->BankAccountEx : [$data->BankAccountEx];
|
|
}
|
|
|
|
$accounts = [];
|
|
foreach ($accountList as $acc) {
|
|
if (! is_object($acc)) {
|
|
continue;
|
|
}
|
|
$bankAccountNum = $acc->BankAccountNum ?? '';
|
|
if (empty($bankAccountNum) || (is_numeric($bankAccountNum) && $bankAccountNum < 0)) {
|
|
continue;
|
|
}
|
|
$accounts[] = [
|
|
'bankAccountNum' => $bankAccountNum,
|
|
'bankCode' => $acc->BankCode ?? '',
|
|
'bankName' => $acc->BankName ?? '',
|
|
];
|
|
}
|
|
|
|
return $accounts;
|
|
}
|
|
|
|
/**
|
|
* 바로빌 API에서 거래내역을 가져와 DB에 캐시
|
|
*/
|
|
protected function fetchAndCache(
|
|
int $tenantId,
|
|
string $userId,
|
|
string $accNum,
|
|
string $bankName,
|
|
string $bankCode,
|
|
string $startDate,
|
|
string $endDate,
|
|
string $yearMonth
|
|
): int {
|
|
$result = $this->soapService->call('bankaccount', 'GetPeriodBankAccountTransLog', [
|
|
'ID' => $userId,
|
|
'BankAccountNum' => $accNum,
|
|
'StartDate' => $startDate,
|
|
'EndDate' => $endDate,
|
|
'TransDirection' => 1,
|
|
'CountPerPage' => 1000,
|
|
'CurrentPage' => 1,
|
|
'OrderDirection' => 2,
|
|
]);
|
|
|
|
if (! $result['success']) {
|
|
$errorCode = $result['error_code'] ?? 0;
|
|
if (in_array($errorCode, [-25005, -25001])) {
|
|
$this->updateSyncStatus($tenantId, $accNum, $yearMonth);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
$chunkData = $result['data'];
|
|
|
|
if (is_numeric($chunkData) && $chunkData < 0) {
|
|
if (in_array((int) $chunkData, [-25005, -25001])) {
|
|
$this->updateSyncStatus($tenantId, $accNum, $yearMonth);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
$rawLogs = [];
|
|
if (isset($chunkData->BankAccountLogList) && isset($chunkData->BankAccountLogList->BankAccountTransLog)) {
|
|
$logs = $chunkData->BankAccountLogList->BankAccountTransLog;
|
|
$rawLogs = is_array($logs) ? $logs : [$logs];
|
|
}
|
|
|
|
$count = 0;
|
|
if (! empty($rawLogs)) {
|
|
$count = $this->cacheTransactions($tenantId, $accNum, $bankName, $bankCode, $rawLogs);
|
|
Log::debug("[BankSync] 캐시 저장 ({$startDate}~{$endDate}): {$count}건");
|
|
}
|
|
|
|
$this->updateSyncStatus($tenantId, $accNum, $yearMonth);
|
|
|
|
return $count;
|
|
}
|
|
|
|
/**
|
|
* API 응답을 DB에 배치 저장
|
|
*/
|
|
protected function cacheTransactions(int $tenantId, string $accNum, string $bankName, string $bankCode, array $rawLogs): int
|
|
{
|
|
$rows = [];
|
|
$now = now();
|
|
|
|
foreach ($rawLogs as $log) {
|
|
$transDT = $log->TransDT ?? '';
|
|
$transDate = strlen($transDT) >= 8 ? substr($transDT, 0, 8) : '';
|
|
$transTime = strlen($transDT) >= 14 ? substr($transDT, 8, 6) : '';
|
|
$deposit = floatval($log->Deposit ?? 0);
|
|
$withdraw = floatval($log->Withdraw ?? 0);
|
|
$balance = floatval($log->Balance ?? 0);
|
|
$summary = $log->TransRemark1 ?? $log->Summary ?? '';
|
|
$remark2 = $log->TransRemark2 ?? '';
|
|
|
|
$cleanSummary = $this->cleanSummary($summary, $remark2);
|
|
|
|
$rows[] = [
|
|
'tenant_id' => $tenantId,
|
|
'bank_account_num' => $log->BankAccountNum ?? $accNum,
|
|
'bank_code' => $log->BankCode ?? $bankCode,
|
|
'bank_name' => $log->BankName ?? $bankName,
|
|
'trans_date' => $transDate,
|
|
'trans_time' => $transTime,
|
|
'trans_dt' => $transDT,
|
|
'deposit' => $deposit,
|
|
'withdraw' => $withdraw,
|
|
'balance' => $balance,
|
|
'summary' => $cleanSummary,
|
|
'cast' => $remark2,
|
|
'memo' => $log->Memo ?? '',
|
|
'trans_office' => $log->TransOffice ?? '',
|
|
'is_manual' => false,
|
|
'created_at' => $now,
|
|
'updated_at' => $now,
|
|
];
|
|
}
|
|
|
|
$inserted = 0;
|
|
foreach (array_chunk($rows, 100) as $batch) {
|
|
$inserted += DB::table('barobill_bank_transactions')->insertOrIgnore($batch);
|
|
}
|
|
|
|
return $inserted;
|
|
}
|
|
|
|
protected function updateSyncStatus(int $tenantId, string $accNum, string $yearMonth): void
|
|
{
|
|
BarobillBankSyncStatus::withoutGlobalScopes()->updateOrCreate(
|
|
[
|
|
'tenant_id' => $tenantId,
|
|
'bank_account_num' => $accNum,
|
|
'synced_year_month' => $yearMonth,
|
|
],
|
|
['synced_at' => now()]
|
|
);
|
|
}
|
|
|
|
/**
|
|
* 기간을 월별 청크로 분할
|
|
*/
|
|
protected function splitDateRangeMonthly(string $startDate, string $endDate): array
|
|
{
|
|
$start = Carbon::createFromFormat('Ymd', $startDate)->startOfDay();
|
|
$end = Carbon::createFromFormat('Ymd', $endDate)->endOfDay();
|
|
|
|
$chunks = [];
|
|
$cursor = $start->copy();
|
|
|
|
while ($cursor->lte($end)) {
|
|
$chunkStart = $cursor->copy();
|
|
$chunkEnd = $cursor->copy()->endOfMonth()->startOfDay();
|
|
|
|
if ($chunkEnd->gt($end)) {
|
|
$chunkEnd = $end->copy()->startOfDay();
|
|
}
|
|
|
|
$chunks[] = [
|
|
'start' => $chunkStart->format('Ymd'),
|
|
'end' => $chunkEnd->format('Ymd'),
|
|
];
|
|
|
|
$cursor = $chunkStart->copy()->addMonth()->startOfMonth();
|
|
}
|
|
|
|
return $chunks;
|
|
}
|
|
|
|
/**
|
|
* 요약 정리 (중복 정보 제거)
|
|
*/
|
|
protected function cleanSummary(string $summary, string $remark): string
|
|
{
|
|
$summary = trim($summary);
|
|
$remark = trim($remark);
|
|
|
|
if (! empty($remark) && str_contains($summary, $remark)) {
|
|
$summary = trim(str_replace($remark, '', $summary));
|
|
}
|
|
|
|
return $summary;
|
|
}
|
|
}
|