refactor: [barobill] 바로빌 연동 코드 전면 개선
- config/services.php에 barobill 설정 등록 (운영/테스트 모드 분기 정상화) - BarobillSetting 모델에 BelongsToTenant 적용 및 use_* 필드 casts 추가 - BarobillService API URL을 baroservice.com(SOAP)으로 수정 - BarobillService callApi 메서드 경로 하드코딩 제거 (서비스별 분기) - BarobillService 예외 이중 래핑 문제 수정 - BarobillController URL 메서드 중복 코드 제거 - 누락 모델 16개 생성 (MNG 패턴 준수, BelongsToTenant 적용) - 바로빌 전 테이블 options JSON 컬럼 추가 마이그레이션
This commit is contained in:
@@ -28,7 +28,7 @@ public function status()
|
||||
'barobill_id' => $setting->barobill_id,
|
||||
'biz_no' => $setting->corp_num,
|
||||
'status' => $setting->isVerified() ? 'active' : 'inactive',
|
||||
'server_mode' => config('services.barobill.test_mode', true) ? 'test' : 'production',
|
||||
'server_mode' => $this->barobillService->isTestMode() ? 'test' : 'production',
|
||||
] : null,
|
||||
];
|
||||
}, __('message.fetched'));
|
||||
@@ -86,17 +86,21 @@ public function signup(Request $request)
|
||||
}, __('message.saved'));
|
||||
}
|
||||
|
||||
/**
|
||||
* 바로빌 서비스 URL 조회 (공통)
|
||||
*/
|
||||
private function getServiceUrl(string $path): array
|
||||
{
|
||||
return ['url' => $this->barobillService->getBaseUrl().$path];
|
||||
}
|
||||
|
||||
/**
|
||||
* 은행 빠른조회 서비스 URL 조회
|
||||
*/
|
||||
public function bankServiceUrl(Request $request)
|
||||
public function bankServiceUrl()
|
||||
{
|
||||
return ApiResponse::handle(function () {
|
||||
$baseUrl = config('services.barobill.test_mode', true)
|
||||
? 'https://testws.barobill.co.kr'
|
||||
: 'https://ws.barobill.co.kr';
|
||||
|
||||
return ['url' => $baseUrl.'/Bank/BankAccountService'];
|
||||
return $this->getServiceUrl('/BANKACCOUNT.asmx');
|
||||
}, __('message.fetched'));
|
||||
}
|
||||
|
||||
@@ -106,11 +110,7 @@ public function bankServiceUrl(Request $request)
|
||||
public function accountLinkUrl()
|
||||
{
|
||||
return ApiResponse::handle(function () {
|
||||
$baseUrl = config('services.barobill.test_mode', true)
|
||||
? 'https://testws.barobill.co.kr'
|
||||
: 'https://ws.barobill.co.kr';
|
||||
|
||||
return ['url' => $baseUrl.'/Bank/AccountLink'];
|
||||
return $this->getServiceUrl('/BANKACCOUNT.asmx');
|
||||
}, __('message.fetched'));
|
||||
}
|
||||
|
||||
@@ -120,11 +120,7 @@ public function accountLinkUrl()
|
||||
public function cardLinkUrl()
|
||||
{
|
||||
return ApiResponse::handle(function () {
|
||||
$baseUrl = config('services.barobill.test_mode', true)
|
||||
? 'https://testws.barobill.co.kr'
|
||||
: 'https://ws.barobill.co.kr';
|
||||
|
||||
return ['url' => $baseUrl.'/Card/CardLink'];
|
||||
return $this->getServiceUrl('/CARD.asmx');
|
||||
}, __('message.fetched'));
|
||||
}
|
||||
|
||||
@@ -134,11 +130,7 @@ public function cardLinkUrl()
|
||||
public function certificateUrl()
|
||||
{
|
||||
return ApiResponse::handle(function () {
|
||||
$baseUrl = config('services.barobill.test_mode', true)
|
||||
? 'https://testws.barobill.co.kr'
|
||||
: 'https://ws.barobill.co.kr';
|
||||
|
||||
return ['url' => $baseUrl.'/Certificate/Register'];
|
||||
return $this->getServiceUrl('/CORPSTATE.asmx');
|
||||
}, __('message.fetched'));
|
||||
}
|
||||
}
|
||||
|
||||
34
app/Models/Barobill/BarobillBankSyncStatus.php
Normal file
34
app/Models/Barobill/BarobillBankSyncStatus.php
Normal file
@@ -0,0 +1,34 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models\Barobill;
|
||||
|
||||
use App\Traits\BelongsToTenant;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
|
||||
class BarobillBankSyncStatus extends Model
|
||||
{
|
||||
use BelongsToTenant;
|
||||
|
||||
protected $table = 'barobill_bank_sync_status';
|
||||
|
||||
protected $fillable = [
|
||||
'tenant_id',
|
||||
'bank_account_num',
|
||||
'synced_year_month',
|
||||
'synced_at',
|
||||
];
|
||||
|
||||
protected $casts = [
|
||||
'synced_at' => 'datetime',
|
||||
];
|
||||
|
||||
// =========================================================================
|
||||
// 관계 정의
|
||||
// =========================================================================
|
||||
|
||||
public function tenant(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(\App\Models\Tenants\Tenant::class);
|
||||
}
|
||||
}
|
||||
97
app/Models/Barobill/BarobillBankTransaction.php
Normal file
97
app/Models/Barobill/BarobillBankTransaction.php
Normal file
@@ -0,0 +1,97 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models\Barobill;
|
||||
|
||||
use App\Traits\BelongsToTenant;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
|
||||
class BarobillBankTransaction extends Model
|
||||
{
|
||||
use BelongsToTenant;
|
||||
|
||||
protected $table = 'barobill_bank_transactions';
|
||||
|
||||
protected $fillable = [
|
||||
'tenant_id',
|
||||
'bank_account_num',
|
||||
'bank_code',
|
||||
'bank_name',
|
||||
'trans_date',
|
||||
'trans_time',
|
||||
'trans_dt',
|
||||
'deposit',
|
||||
'withdraw',
|
||||
'balance',
|
||||
'summary',
|
||||
'cast',
|
||||
'memo',
|
||||
'trans_office',
|
||||
'account_code',
|
||||
'account_name',
|
||||
'is_manual',
|
||||
'client_code',
|
||||
'client_name',
|
||||
];
|
||||
|
||||
protected $casts = [
|
||||
'deposit' => 'decimal:2',
|
||||
'withdraw' => 'decimal:2',
|
||||
'balance' => 'decimal:2',
|
||||
'is_manual' => 'boolean',
|
||||
];
|
||||
|
||||
// =========================================================================
|
||||
// 관계 정의
|
||||
// =========================================================================
|
||||
|
||||
public function tenant(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(\App\Models\Tenants\Tenant::class);
|
||||
}
|
||||
|
||||
// =========================================================================
|
||||
// 접근자
|
||||
// =========================================================================
|
||||
|
||||
/**
|
||||
* 거래 고유 키 (계좌번호|거래일시|입금|출금|잔액)
|
||||
*/
|
||||
public function getUniqueKeyAttribute(): string
|
||||
{
|
||||
return static::generateUniqueKey([
|
||||
'bank_account_num' => $this->bank_account_num,
|
||||
'trans_dt' => $this->trans_dt,
|
||||
'deposit' => $this->deposit,
|
||||
'withdraw' => $this->withdraw,
|
||||
'balance' => $this->balance,
|
||||
]);
|
||||
}
|
||||
|
||||
// =========================================================================
|
||||
// 헬퍼 메서드
|
||||
// =========================================================================
|
||||
|
||||
public static function generateUniqueKey(array $data): string
|
||||
{
|
||||
return implode('|', [
|
||||
$data['bank_account_num'] ?? '',
|
||||
$data['trans_dt'] ?? '',
|
||||
$data['deposit'] ?? '0',
|
||||
$data['withdraw'] ?? '0',
|
||||
$data['balance'] ?? '0',
|
||||
]);
|
||||
}
|
||||
|
||||
public static function getByDateRange(int $tenantId, string $startDate, string $endDate, ?string $accountNum = null)
|
||||
{
|
||||
$query = static::where('tenant_id', $tenantId)
|
||||
->whereBetween('trans_date', [$startDate, $endDate]);
|
||||
|
||||
if ($accountNum) {
|
||||
$query->where('bank_account_num', $accountNum);
|
||||
}
|
||||
|
||||
return $query->orderBy('trans_date')->orderBy('trans_dt')->get();
|
||||
}
|
||||
}
|
||||
49
app/Models/Barobill/BarobillBankTransactionOverride.php
Normal file
49
app/Models/Barobill/BarobillBankTransactionOverride.php
Normal file
@@ -0,0 +1,49 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models\Barobill;
|
||||
|
||||
use App\Traits\BelongsToTenant;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
class BarobillBankTransactionOverride extends Model
|
||||
{
|
||||
use BelongsToTenant;
|
||||
|
||||
protected $table = 'barobill_bank_transaction_overrides';
|
||||
|
||||
protected $fillable = [
|
||||
'tenant_id',
|
||||
'unique_key',
|
||||
'modified_summary',
|
||||
'modified_cast',
|
||||
];
|
||||
|
||||
// =========================================================================
|
||||
// 스코프
|
||||
// =========================================================================
|
||||
|
||||
public function scopeByUniqueKey($query, string $uniqueKey)
|
||||
{
|
||||
return $query->where('unique_key', $uniqueKey);
|
||||
}
|
||||
|
||||
// =========================================================================
|
||||
// 헬퍼 메서드
|
||||
// =========================================================================
|
||||
|
||||
public static function getByUniqueKeys(int $tenantId, array $uniqueKeys)
|
||||
{
|
||||
return static::where('tenant_id', $tenantId)
|
||||
->whereIn('unique_key', $uniqueKeys)
|
||||
->get()
|
||||
->keyBy('unique_key');
|
||||
}
|
||||
|
||||
public static function saveOverride(int $tenantId, string $uniqueKey, ?string $modifiedSummary, ?string $modifiedCast): self
|
||||
{
|
||||
return static::updateOrCreate(
|
||||
['tenant_id' => $tenantId, 'unique_key' => $uniqueKey],
|
||||
['modified_summary' => $modifiedSummary, 'modified_cast' => $modifiedCast]
|
||||
);
|
||||
}
|
||||
}
|
||||
69
app/Models/Barobill/BarobillBankTransactionSplit.php
Normal file
69
app/Models/Barobill/BarobillBankTransactionSplit.php
Normal file
@@ -0,0 +1,69 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models\Barobill;
|
||||
|
||||
use App\Traits\BelongsToTenant;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
|
||||
class BarobillBankTransactionSplit extends Model
|
||||
{
|
||||
use BelongsToTenant;
|
||||
|
||||
protected $table = 'barobill_bank_transaction_splits';
|
||||
|
||||
protected $fillable = [
|
||||
'tenant_id',
|
||||
'original_unique_key',
|
||||
'split_amount',
|
||||
'account_code',
|
||||
'account_name',
|
||||
'description',
|
||||
'memo',
|
||||
'sort_order',
|
||||
'bank_account_num',
|
||||
'trans_dt',
|
||||
'trans_date',
|
||||
'original_deposit',
|
||||
'original_withdraw',
|
||||
'summary',
|
||||
];
|
||||
|
||||
protected $casts = [
|
||||
'split_amount' => 'decimal:2',
|
||||
'original_deposit' => 'decimal:2',
|
||||
'original_withdraw' => 'decimal:2',
|
||||
'sort_order' => 'integer',
|
||||
];
|
||||
|
||||
// =========================================================================
|
||||
// 관계 정의
|
||||
// =========================================================================
|
||||
|
||||
public function tenant(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(\App\Models\Tenants\Tenant::class);
|
||||
}
|
||||
|
||||
// =========================================================================
|
||||
// 헬퍼 메서드
|
||||
// =========================================================================
|
||||
|
||||
public static function getByDateRange(int $tenantId, string $startDate, string $endDate)
|
||||
{
|
||||
return static::where('tenant_id', $tenantId)
|
||||
->whereBetween('trans_date', [$startDate, $endDate])
|
||||
->orderBy('original_unique_key')
|
||||
->orderBy('sort_order')
|
||||
->get()
|
||||
->groupBy('original_unique_key');
|
||||
}
|
||||
|
||||
public static function getByUniqueKey(int $tenantId, string $uniqueKey)
|
||||
{
|
||||
return static::where('tenant_id', $tenantId)
|
||||
->where('original_unique_key', $uniqueKey)
|
||||
->orderBy('sort_order')
|
||||
->get();
|
||||
}
|
||||
}
|
||||
82
app/Models/Barobill/BarobillBillingRecord.php
Normal file
82
app/Models/Barobill/BarobillBillingRecord.php
Normal file
@@ -0,0 +1,82 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models\Barobill;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
|
||||
class BarobillBillingRecord extends Model
|
||||
{
|
||||
protected $table = 'barobill_billing_records';
|
||||
|
||||
public const SERVICE_TYPES = ['tax_invoice', 'bank_account', 'card', 'hometax'];
|
||||
|
||||
public const BILLING_TYPES = ['subscription', 'usage'];
|
||||
|
||||
protected $fillable = [
|
||||
'member_id',
|
||||
'billing_month',
|
||||
'service_type',
|
||||
'billing_type',
|
||||
'quantity',
|
||||
'unit_price',
|
||||
'total_amount',
|
||||
'billed_at',
|
||||
'description',
|
||||
];
|
||||
|
||||
protected $casts = [
|
||||
'quantity' => 'integer',
|
||||
'unit_price' => 'integer',
|
||||
'total_amount' => 'integer',
|
||||
'billed_at' => 'date',
|
||||
];
|
||||
|
||||
// =========================================================================
|
||||
// 관계 정의
|
||||
// =========================================================================
|
||||
|
||||
public function member(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(BarobillMember::class, 'member_id');
|
||||
}
|
||||
|
||||
// =========================================================================
|
||||
// 스코프
|
||||
// =========================================================================
|
||||
|
||||
public function scopeOfMonth($query, string $billingMonth)
|
||||
{
|
||||
return $query->where('billing_month', $billingMonth);
|
||||
}
|
||||
|
||||
public function scopeSubscription($query)
|
||||
{
|
||||
return $query->where('billing_type', 'subscription');
|
||||
}
|
||||
|
||||
public function scopeUsage($query)
|
||||
{
|
||||
return $query->where('billing_type', 'usage');
|
||||
}
|
||||
|
||||
public function scopeOfService($query, string $serviceType)
|
||||
{
|
||||
return $query->where('service_type', $serviceType);
|
||||
}
|
||||
|
||||
// =========================================================================
|
||||
// 접근자
|
||||
// =========================================================================
|
||||
|
||||
public function getServiceTypeLabelAttribute(): string
|
||||
{
|
||||
return match ($this->service_type) {
|
||||
'tax_invoice' => '전자세금계산서',
|
||||
'bank_account' => '계좌조회',
|
||||
'card' => '카드조회',
|
||||
'hometax' => '홈택스',
|
||||
default => $this->service_type,
|
||||
};
|
||||
}
|
||||
}
|
||||
108
app/Models/Barobill/BarobillCardTransaction.php
Normal file
108
app/Models/Barobill/BarobillCardTransaction.php
Normal file
@@ -0,0 +1,108 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models\Barobill;
|
||||
|
||||
use App\Traits\BelongsToTenant;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
|
||||
class BarobillCardTransaction extends Model
|
||||
{
|
||||
use BelongsToTenant;
|
||||
|
||||
protected $table = 'barobill_card_transactions';
|
||||
|
||||
protected $fillable = [
|
||||
'tenant_id',
|
||||
'card_num',
|
||||
'card_company',
|
||||
'card_company_name',
|
||||
'use_dt',
|
||||
'use_date',
|
||||
'use_time',
|
||||
'approval_num',
|
||||
'approval_type',
|
||||
'approval_amount',
|
||||
'tax',
|
||||
'service_charge',
|
||||
'payment_plan',
|
||||
'currency_code',
|
||||
'merchant_name',
|
||||
'merchant_biz_num',
|
||||
'merchant_addr',
|
||||
'merchant_ceo',
|
||||
'merchant_biz_type',
|
||||
'merchant_tel',
|
||||
'memo',
|
||||
'use_key',
|
||||
'account_code',
|
||||
'account_name',
|
||||
'deduction_type',
|
||||
'evidence_name',
|
||||
'description',
|
||||
'modified_supply_amount',
|
||||
'modified_tax',
|
||||
'is_manual',
|
||||
];
|
||||
|
||||
protected $casts = [
|
||||
'approval_amount' => 'decimal:2',
|
||||
'tax' => 'decimal:2',
|
||||
'service_charge' => 'decimal:2',
|
||||
'modified_supply_amount' => 'decimal:2',
|
||||
'modified_tax' => 'decimal:2',
|
||||
'is_manual' => 'boolean',
|
||||
];
|
||||
|
||||
// =========================================================================
|
||||
// 관계 정의
|
||||
// =========================================================================
|
||||
|
||||
public function tenant(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(\App\Models\Tenants\Tenant::class);
|
||||
}
|
||||
|
||||
// =========================================================================
|
||||
// 접근자
|
||||
// =========================================================================
|
||||
|
||||
/**
|
||||
* 거래 고유 키 (cardNum|useDt|approvalNum|approvalAmount)
|
||||
*/
|
||||
public function getUniqueKeyAttribute(): string
|
||||
{
|
||||
return static::generateUniqueKey([
|
||||
'card_num' => $this->card_num,
|
||||
'use_dt' => $this->use_dt,
|
||||
'approval_num' => $this->approval_num,
|
||||
'approval_amount' => $this->approval_amount,
|
||||
]);
|
||||
}
|
||||
|
||||
// =========================================================================
|
||||
// 헬퍼 메서드
|
||||
// =========================================================================
|
||||
|
||||
public static function generateUniqueKey(array $data): string
|
||||
{
|
||||
return implode('|', [
|
||||
$data['card_num'] ?? '',
|
||||
$data['use_dt'] ?? '',
|
||||
$data['approval_num'] ?? '',
|
||||
$data['approval_amount'] ?? '0',
|
||||
]);
|
||||
}
|
||||
|
||||
public static function getByDateRange(int $tenantId, string $startDate, string $endDate, ?string $cardNum = null)
|
||||
{
|
||||
$query = static::where('tenant_id', $tenantId)
|
||||
->whereBetween('use_date', [$startDate, $endDate]);
|
||||
|
||||
if ($cardNum) {
|
||||
$query->where('card_num', $cardNum);
|
||||
}
|
||||
|
||||
return $query->orderBy('use_date')->orderBy('use_dt')->get();
|
||||
}
|
||||
}
|
||||
41
app/Models/Barobill/BarobillCardTransactionAmountLog.php
Normal file
41
app/Models/Barobill/BarobillCardTransactionAmountLog.php
Normal file
@@ -0,0 +1,41 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models\Barobill;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
|
||||
class BarobillCardTransactionAmountLog extends Model
|
||||
{
|
||||
protected $table = 'barobill_card_transaction_amount_logs';
|
||||
|
||||
public $timestamps = false;
|
||||
|
||||
protected $fillable = [
|
||||
'card_transaction_id',
|
||||
'original_unique_key',
|
||||
'before_supply_amount',
|
||||
'before_tax',
|
||||
'after_supply_amount',
|
||||
'after_tax',
|
||||
'modified_by',
|
||||
'modified_by_name',
|
||||
'ip_address',
|
||||
];
|
||||
|
||||
protected $casts = [
|
||||
'before_supply_amount' => 'decimal:2',
|
||||
'before_tax' => 'decimal:2',
|
||||
'after_supply_amount' => 'decimal:2',
|
||||
'after_tax' => 'decimal:2',
|
||||
];
|
||||
|
||||
// =========================================================================
|
||||
// 관계 정의
|
||||
// =========================================================================
|
||||
|
||||
public function cardTransaction(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(BarobillCardTransaction::class, 'card_transaction_id');
|
||||
}
|
||||
}
|
||||
61
app/Models/Barobill/BarobillCardTransactionHide.php
Normal file
61
app/Models/Barobill/BarobillCardTransactionHide.php
Normal file
@@ -0,0 +1,61 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models\Barobill;
|
||||
|
||||
use App\Traits\BelongsToTenant;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
class BarobillCardTransactionHide extends Model
|
||||
{
|
||||
use BelongsToTenant;
|
||||
|
||||
protected $table = 'barobill_card_transaction_hides';
|
||||
|
||||
protected $fillable = [
|
||||
'tenant_id',
|
||||
'original_unique_key',
|
||||
'card_num',
|
||||
'use_date',
|
||||
'approval_num',
|
||||
'original_amount',
|
||||
'merchant_name',
|
||||
'hidden_by',
|
||||
];
|
||||
|
||||
protected $casts = [
|
||||
'original_amount' => 'decimal:2',
|
||||
];
|
||||
|
||||
// =========================================================================
|
||||
// 헬퍼 메서드
|
||||
// =========================================================================
|
||||
|
||||
public static function getHiddenKeys(int $tenantId, string $startDate, string $endDate)
|
||||
{
|
||||
return static::where('tenant_id', $tenantId)
|
||||
->whereBetween('use_date', [$startDate, $endDate])
|
||||
->pluck('original_unique_key')
|
||||
->toArray();
|
||||
}
|
||||
|
||||
public static function hideTransaction(int $tenantId, string $uniqueKey, array $originalData, int $userId): self
|
||||
{
|
||||
return static::create([
|
||||
'tenant_id' => $tenantId,
|
||||
'original_unique_key' => $uniqueKey,
|
||||
'card_num' => $originalData['card_num'] ?? '',
|
||||
'use_date' => $originalData['use_date'] ?? '',
|
||||
'approval_num' => $originalData['approval_num'] ?? '',
|
||||
'original_amount' => $originalData['approval_amount'] ?? 0,
|
||||
'merchant_name' => $originalData['merchant_name'] ?? '',
|
||||
'hidden_by' => $userId,
|
||||
]);
|
||||
}
|
||||
|
||||
public static function restoreTransaction(int $tenantId, string $uniqueKey): bool
|
||||
{
|
||||
return static::where('tenant_id', $tenantId)
|
||||
->where('original_unique_key', $uniqueKey)
|
||||
->delete() > 0;
|
||||
}
|
||||
}
|
||||
74
app/Models/Barobill/BarobillCardTransactionSplit.php
Normal file
74
app/Models/Barobill/BarobillCardTransactionSplit.php
Normal file
@@ -0,0 +1,74 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models\Barobill;
|
||||
|
||||
use App\Traits\BelongsToTenant;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
|
||||
class BarobillCardTransactionSplit extends Model
|
||||
{
|
||||
use BelongsToTenant;
|
||||
|
||||
protected $table = 'barobill_card_transaction_splits';
|
||||
|
||||
protected $fillable = [
|
||||
'tenant_id',
|
||||
'original_unique_key',
|
||||
'split_amount',
|
||||
'split_supply_amount',
|
||||
'split_tax',
|
||||
'account_code',
|
||||
'account_name',
|
||||
'deduction_type',
|
||||
'evidence_name',
|
||||
'description',
|
||||
'memo',
|
||||
'sort_order',
|
||||
'card_num',
|
||||
'use_dt',
|
||||
'use_date',
|
||||
'approval_num',
|
||||
'original_amount',
|
||||
'merchant_name',
|
||||
];
|
||||
|
||||
protected $casts = [
|
||||
'split_amount' => 'decimal:2',
|
||||
'split_supply_amount' => 'decimal:2',
|
||||
'split_tax' => 'decimal:2',
|
||||
'original_amount' => 'decimal:2',
|
||||
'sort_order' => 'integer',
|
||||
];
|
||||
|
||||
// =========================================================================
|
||||
// 관계 정의
|
||||
// =========================================================================
|
||||
|
||||
public function tenant(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(\App\Models\Tenants\Tenant::class);
|
||||
}
|
||||
|
||||
// =========================================================================
|
||||
// 헬퍼 메서드
|
||||
// =========================================================================
|
||||
|
||||
public static function getByDateRange(int $tenantId, string $startDate, string $endDate)
|
||||
{
|
||||
return static::where('tenant_id', $tenantId)
|
||||
->whereBetween('use_date', [$startDate, $endDate])
|
||||
->orderBy('original_unique_key')
|
||||
->orderBy('sort_order')
|
||||
->get()
|
||||
->groupBy('original_unique_key');
|
||||
}
|
||||
|
||||
public static function getByUniqueKey(int $tenantId, string $uniqueKey)
|
||||
{
|
||||
return static::where('tenant_id', $tenantId)
|
||||
->where('original_unique_key', $uniqueKey)
|
||||
->orderBy('sort_order')
|
||||
->get();
|
||||
}
|
||||
}
|
||||
61
app/Models/Barobill/BarobillConfig.php
Normal file
61
app/Models/Barobill/BarobillConfig.php
Normal file
@@ -0,0 +1,61 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models\Barobill;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\SoftDeletes;
|
||||
|
||||
class BarobillConfig extends Model
|
||||
{
|
||||
use SoftDeletes;
|
||||
|
||||
protected $table = 'barobill_configs';
|
||||
|
||||
protected $fillable = [
|
||||
'name',
|
||||
'environment',
|
||||
'cert_key',
|
||||
'corp_num',
|
||||
'base_url',
|
||||
'description',
|
||||
'is_active',
|
||||
];
|
||||
|
||||
protected $casts = [
|
||||
'is_active' => 'boolean',
|
||||
];
|
||||
|
||||
// =========================================================================
|
||||
// 헬퍼 메서드
|
||||
// =========================================================================
|
||||
|
||||
public static function getActiveTest(): ?self
|
||||
{
|
||||
return static::where('environment', 'test')->where('is_active', true)->first();
|
||||
}
|
||||
|
||||
public static function getActiveProduction(): ?self
|
||||
{
|
||||
return static::where('environment', 'production')->where('is_active', true)->first();
|
||||
}
|
||||
|
||||
public static function getActive(bool $isTestMode): ?self
|
||||
{
|
||||
return $isTestMode ? static::getActiveTest() : static::getActiveProduction();
|
||||
}
|
||||
|
||||
public function getEnvironmentLabelAttribute(): string
|
||||
{
|
||||
return $this->environment === 'test' ? '테스트' : '운영';
|
||||
}
|
||||
|
||||
public function getMaskedCertKeyAttribute(): string
|
||||
{
|
||||
$key = $this->cert_key;
|
||||
if (strlen($key) <= 8) {
|
||||
return str_repeat('*', strlen($key));
|
||||
}
|
||||
|
||||
return substr($key, 0, 4).'****'.substr($key, -4);
|
||||
}
|
||||
}
|
||||
82
app/Models/Barobill/BarobillMember.php
Normal file
82
app/Models/Barobill/BarobillMember.php
Normal file
@@ -0,0 +1,82 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models\Barobill;
|
||||
|
||||
use App\Traits\BelongsToTenant;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
use Illuminate\Database\Eloquent\SoftDeletes;
|
||||
|
||||
class BarobillMember extends Model
|
||||
{
|
||||
use BelongsToTenant, SoftDeletes;
|
||||
|
||||
protected $table = 'barobill_members';
|
||||
|
||||
protected $fillable = [
|
||||
'tenant_id',
|
||||
'biz_no',
|
||||
'corp_name',
|
||||
'ceo_name',
|
||||
'addr',
|
||||
'biz_type',
|
||||
'biz_class',
|
||||
'barobill_id',
|
||||
'barobill_pwd',
|
||||
'manager_name',
|
||||
'manager_email',
|
||||
'manager_hp',
|
||||
'status',
|
||||
'server_mode',
|
||||
'last_sales_fetch_at',
|
||||
'last_purchases_fetch_at',
|
||||
];
|
||||
|
||||
protected $casts = [
|
||||
'barobill_pwd' => 'encrypted',
|
||||
'last_sales_fetch_at' => 'datetime',
|
||||
'last_purchases_fetch_at' => 'datetime',
|
||||
];
|
||||
|
||||
protected $hidden = [
|
||||
'barobill_pwd',
|
||||
];
|
||||
|
||||
// =========================================================================
|
||||
// 관계 정의
|
||||
// =========================================================================
|
||||
|
||||
public function tenant(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(\App\Models\Tenants\Tenant::class);
|
||||
}
|
||||
|
||||
// =========================================================================
|
||||
// 접근자
|
||||
// =========================================================================
|
||||
|
||||
public function getFormattedBizNoAttribute(): string
|
||||
{
|
||||
$num = $this->biz_no;
|
||||
if (strlen($num) === 10) {
|
||||
return substr($num, 0, 3).'-'.substr($num, 3, 2).'-'.substr($num, 5);
|
||||
}
|
||||
|
||||
return $num ?? '';
|
||||
}
|
||||
|
||||
public function getStatusLabelAttribute(): string
|
||||
{
|
||||
return match ($this->status) {
|
||||
'active' => '활성',
|
||||
'inactive' => '비활성',
|
||||
'pending' => '대기',
|
||||
default => $this->status,
|
||||
};
|
||||
}
|
||||
|
||||
public function isTestMode(): bool
|
||||
{
|
||||
return $this->server_mode === 'test';
|
||||
}
|
||||
}
|
||||
53
app/Models/Barobill/BarobillMonthlySummary.php
Normal file
53
app/Models/Barobill/BarobillMonthlySummary.php
Normal file
@@ -0,0 +1,53 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models\Barobill;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
|
||||
class BarobillMonthlySummary extends Model
|
||||
{
|
||||
protected $table = 'barobill_monthly_summaries';
|
||||
|
||||
protected $fillable = [
|
||||
'member_id',
|
||||
'billing_month',
|
||||
'bank_account_fee',
|
||||
'card_fee',
|
||||
'hometax_fee',
|
||||
'subscription_total',
|
||||
'tax_invoice_count',
|
||||
'tax_invoice_amount',
|
||||
'usage_total',
|
||||
'grand_total',
|
||||
];
|
||||
|
||||
protected $casts = [
|
||||
'bank_account_fee' => 'integer',
|
||||
'card_fee' => 'integer',
|
||||
'hometax_fee' => 'integer',
|
||||
'subscription_total' => 'integer',
|
||||
'tax_invoice_count' => 'integer',
|
||||
'tax_invoice_amount' => 'integer',
|
||||
'usage_total' => 'integer',
|
||||
'grand_total' => 'integer',
|
||||
];
|
||||
|
||||
// =========================================================================
|
||||
// 관계 정의
|
||||
// =========================================================================
|
||||
|
||||
public function member(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(BarobillMember::class, 'member_id');
|
||||
}
|
||||
|
||||
// =========================================================================
|
||||
// 스코프
|
||||
// =========================================================================
|
||||
|
||||
public function scopeOfMonth($query, string $billingMonth)
|
||||
{
|
||||
return $query->where('billing_month', $billingMonth);
|
||||
}
|
||||
}
|
||||
82
app/Models/Barobill/BarobillPricingPolicy.php
Normal file
82
app/Models/Barobill/BarobillPricingPolicy.php
Normal file
@@ -0,0 +1,82 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models\Barobill;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
class BarobillPricingPolicy extends Model
|
||||
{
|
||||
protected $table = 'barobill_pricing_policies';
|
||||
|
||||
public const TYPE_CARD = 'card';
|
||||
|
||||
public const TYPE_TAX_INVOICE = 'tax_invoice';
|
||||
|
||||
public const TYPE_BANK_ACCOUNT = 'bank_account';
|
||||
|
||||
protected $fillable = [
|
||||
'service_type',
|
||||
'name',
|
||||
'description',
|
||||
'free_quota',
|
||||
'free_quota_unit',
|
||||
'additional_unit',
|
||||
'additional_unit_label',
|
||||
'additional_price',
|
||||
'is_active',
|
||||
'sort_order',
|
||||
];
|
||||
|
||||
protected $casts = [
|
||||
'free_quota' => 'integer',
|
||||
'additional_unit' => 'integer',
|
||||
'additional_price' => 'integer',
|
||||
'is_active' => 'boolean',
|
||||
'sort_order' => 'integer',
|
||||
];
|
||||
|
||||
// =========================================================================
|
||||
// 스코프
|
||||
// =========================================================================
|
||||
|
||||
public function scopeActive($query)
|
||||
{
|
||||
return $query->where('is_active', true);
|
||||
}
|
||||
|
||||
// =========================================================================
|
||||
// 헬퍼 메서드
|
||||
// =========================================================================
|
||||
|
||||
public static function getByServiceType(string $serviceType): ?self
|
||||
{
|
||||
return static::active()->where('service_type', $serviceType)->first();
|
||||
}
|
||||
|
||||
public static function getAllActive()
|
||||
{
|
||||
return static::active()->orderBy('sort_order')->get();
|
||||
}
|
||||
|
||||
public function getServiceTypeLabelAttribute(): string
|
||||
{
|
||||
return match ($this->service_type) {
|
||||
self::TYPE_CARD => '카드조회',
|
||||
self::TYPE_TAX_INVOICE => '전자세금계산서',
|
||||
self::TYPE_BANK_ACCOUNT => '계좌조회',
|
||||
default => $this->service_type,
|
||||
};
|
||||
}
|
||||
|
||||
public function calculateBilling(int $usageCount): int
|
||||
{
|
||||
if ($usageCount <= $this->free_quota) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
$excess = $usageCount - $this->free_quota;
|
||||
$units = (int) ceil($excess / max($this->additional_unit, 1));
|
||||
|
||||
return $units * $this->additional_price;
|
||||
}
|
||||
}
|
||||
76
app/Models/Barobill/BarobillSubscription.php
Normal file
76
app/Models/Barobill/BarobillSubscription.php
Normal file
@@ -0,0 +1,76 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models\Barobill;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
use Illuminate\Database\Eloquent\SoftDeletes;
|
||||
|
||||
class BarobillSubscription extends Model
|
||||
{
|
||||
use SoftDeletes;
|
||||
|
||||
protected $table = 'barobill_subscriptions';
|
||||
|
||||
public const SERVICE_TYPES = ['bank_account', 'card', 'hometax'];
|
||||
|
||||
public const DEFAULT_MONTHLY_FEES = [
|
||||
'bank_account' => 10000,
|
||||
'card' => 10000,
|
||||
'hometax' => 0,
|
||||
];
|
||||
|
||||
protected $fillable = [
|
||||
'member_id',
|
||||
'service_type',
|
||||
'monthly_fee',
|
||||
'started_at',
|
||||
'ended_at',
|
||||
'is_active',
|
||||
'memo',
|
||||
];
|
||||
|
||||
protected $casts = [
|
||||
'monthly_fee' => 'integer',
|
||||
'started_at' => 'date',
|
||||
'ended_at' => 'date',
|
||||
'is_active' => 'boolean',
|
||||
];
|
||||
|
||||
// =========================================================================
|
||||
// 관계 정의
|
||||
// =========================================================================
|
||||
|
||||
public function member(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(BarobillMember::class, 'member_id');
|
||||
}
|
||||
|
||||
// =========================================================================
|
||||
// 스코프
|
||||
// =========================================================================
|
||||
|
||||
public function scopeActive($query)
|
||||
{
|
||||
return $query->where('is_active', true);
|
||||
}
|
||||
|
||||
public function scopeOfService($query, string $serviceType)
|
||||
{
|
||||
return $query->where('service_type', $serviceType);
|
||||
}
|
||||
|
||||
// =========================================================================
|
||||
// 접근자
|
||||
// =========================================================================
|
||||
|
||||
public function getServiceTypeLabelAttribute(): string
|
||||
{
|
||||
return match ($this->service_type) {
|
||||
'bank_account' => '계좌조회',
|
||||
'card' => '카드조회',
|
||||
'hometax' => '홈택스',
|
||||
default => $this->service_type,
|
||||
};
|
||||
}
|
||||
}
|
||||
158
app/Models/Barobill/HometaxInvoice.php
Normal file
158
app/Models/Barobill/HometaxInvoice.php
Normal file
@@ -0,0 +1,158 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models\Barobill;
|
||||
|
||||
use App\Traits\BelongsToTenant;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
use Illuminate\Database\Eloquent\Relations\HasMany;
|
||||
use Illuminate\Database\Eloquent\SoftDeletes;
|
||||
|
||||
class HometaxInvoice extends Model
|
||||
{
|
||||
use BelongsToTenant, SoftDeletes;
|
||||
|
||||
protected $table = 'hometax_invoices';
|
||||
|
||||
// 과세유형
|
||||
public const TAX_TYPE_TAXABLE = '01'; // 과세
|
||||
|
||||
public const TAX_TYPE_ZERO = '02'; // 영세
|
||||
|
||||
public const TAX_TYPE_EXEMPT = '03'; // 면세
|
||||
|
||||
// 영수/청구
|
||||
public const PURPOSE_TYPE_RECEIPT = '01'; // 영수
|
||||
|
||||
public const PURPOSE_TYPE_CLAIM = '02'; // 청구
|
||||
|
||||
// 발급유형
|
||||
public const ISSUE_TYPE_NORMAL = '01'; // 정발행
|
||||
|
||||
public const ISSUE_TYPE_REVERSE = '02'; // 역발행
|
||||
|
||||
protected $fillable = [
|
||||
'tenant_id',
|
||||
'nts_confirm_num',
|
||||
'invoice_type',
|
||||
'write_date',
|
||||
'issue_date',
|
||||
'send_date',
|
||||
'invoicer_corp_num',
|
||||
'invoicer_tax_reg_id',
|
||||
'invoicer_corp_name',
|
||||
'invoicer_ceo_name',
|
||||
'invoicer_addr',
|
||||
'invoicer_biz_type',
|
||||
'invoicer_biz_class',
|
||||
'invoicer_contact_id',
|
||||
'invoicee_corp_num',
|
||||
'invoicee_tax_reg_id',
|
||||
'invoicee_corp_name',
|
||||
'invoicee_ceo_name',
|
||||
'invoicee_addr',
|
||||
'invoicee_biz_type',
|
||||
'invoicee_biz_class',
|
||||
'invoicee_contact_id',
|
||||
'supply_amount',
|
||||
'tax_amount',
|
||||
'total_amount',
|
||||
'tax_type',
|
||||
'purpose_type',
|
||||
'issue_type',
|
||||
'is_modified',
|
||||
'original_nts_confirm_num',
|
||||
'modify_code',
|
||||
'remark1',
|
||||
'remark2',
|
||||
'remark3',
|
||||
'item_name',
|
||||
'item_count',
|
||||
'item_unit_price',
|
||||
'item_supply_amount',
|
||||
'item_tax_amount',
|
||||
'item_remark',
|
||||
'account_code',
|
||||
'account_name',
|
||||
'deduction_type',
|
||||
];
|
||||
|
||||
protected $casts = [
|
||||
'supply_amount' => 'integer',
|
||||
'tax_amount' => 'integer',
|
||||
'total_amount' => 'integer',
|
||||
'item_count' => 'integer',
|
||||
'item_unit_price' => 'integer',
|
||||
'item_supply_amount' => 'integer',
|
||||
'item_tax_amount' => 'integer',
|
||||
'is_modified' => 'boolean',
|
||||
'write_date' => 'date',
|
||||
'issue_date' => 'date',
|
||||
'send_date' => 'date',
|
||||
];
|
||||
|
||||
// =========================================================================
|
||||
// 관계 정의
|
||||
// =========================================================================
|
||||
|
||||
public function tenant(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(\App\Models\Tenants\Tenant::class);
|
||||
}
|
||||
|
||||
public function journals(): HasMany
|
||||
{
|
||||
return $this->hasMany(HometaxInvoiceJournal::class, 'hometax_invoice_id');
|
||||
}
|
||||
|
||||
// =========================================================================
|
||||
// 스코프
|
||||
// =========================================================================
|
||||
|
||||
public function scopeSales($query)
|
||||
{
|
||||
return $query->where('invoice_type', 'sales');
|
||||
}
|
||||
|
||||
public function scopePurchase($query)
|
||||
{
|
||||
return $query->where('invoice_type', 'purchase');
|
||||
}
|
||||
|
||||
public function scopePeriod($query, string $startDate, string $endDate)
|
||||
{
|
||||
return $query->whereBetween('write_date', [$startDate, $endDate]);
|
||||
}
|
||||
|
||||
// =========================================================================
|
||||
// 접근자
|
||||
// =========================================================================
|
||||
|
||||
public function getTaxTypeNameAttribute(): string
|
||||
{
|
||||
return match ($this->tax_type) {
|
||||
self::TAX_TYPE_TAXABLE => '과세',
|
||||
self::TAX_TYPE_ZERO => '영세',
|
||||
self::TAX_TYPE_EXEMPT => '면세',
|
||||
default => $this->tax_type ?? '',
|
||||
};
|
||||
}
|
||||
|
||||
public function getPurposeTypeNameAttribute(): string
|
||||
{
|
||||
return match ($this->purpose_type) {
|
||||
self::PURPOSE_TYPE_RECEIPT => '영수',
|
||||
self::PURPOSE_TYPE_CLAIM => '청구',
|
||||
default => $this->purpose_type ?? '',
|
||||
};
|
||||
}
|
||||
|
||||
public function getIssueTypeNameAttribute(): string
|
||||
{
|
||||
return match ($this->issue_type) {
|
||||
self::ISSUE_TYPE_NORMAL => '정발행',
|
||||
self::ISSUE_TYPE_REVERSE => '역발행',
|
||||
default => $this->issue_type ?? '',
|
||||
};
|
||||
}
|
||||
}
|
||||
78
app/Models/Barobill/HometaxInvoiceJournal.php
Normal file
78
app/Models/Barobill/HometaxInvoiceJournal.php
Normal file
@@ -0,0 +1,78 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models\Barobill;
|
||||
|
||||
use App\Traits\BelongsToTenant;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
|
||||
class HometaxInvoiceJournal extends Model
|
||||
{
|
||||
use BelongsToTenant;
|
||||
|
||||
protected $table = 'hometax_invoice_journals';
|
||||
|
||||
protected $fillable = [
|
||||
'tenant_id',
|
||||
'hometax_invoice_id',
|
||||
'nts_confirm_num',
|
||||
'dc_type',
|
||||
'account_code',
|
||||
'account_name',
|
||||
'debit_amount',
|
||||
'credit_amount',
|
||||
'description',
|
||||
'sort_order',
|
||||
'invoice_type',
|
||||
'write_date',
|
||||
'supply_amount',
|
||||
'tax_amount',
|
||||
'total_amount',
|
||||
'trading_partner_name',
|
||||
];
|
||||
|
||||
protected $casts = [
|
||||
'debit_amount' => 'integer',
|
||||
'credit_amount' => 'integer',
|
||||
'sort_order' => 'integer',
|
||||
'supply_amount' => 'integer',
|
||||
'tax_amount' => 'integer',
|
||||
'total_amount' => 'integer',
|
||||
'write_date' => 'date',
|
||||
];
|
||||
|
||||
// =========================================================================
|
||||
// 관계 정의
|
||||
// =========================================================================
|
||||
|
||||
public function tenant(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(\App\Models\Tenants\Tenant::class);
|
||||
}
|
||||
|
||||
public function invoice(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(HometaxInvoice::class, 'hometax_invoice_id');
|
||||
}
|
||||
|
||||
// =========================================================================
|
||||
// 헬퍼 메서드
|
||||
// =========================================================================
|
||||
|
||||
public static function getByInvoiceId(int $tenantId, int $invoiceId)
|
||||
{
|
||||
return static::where('tenant_id', $tenantId)
|
||||
->where('hometax_invoice_id', $invoiceId)
|
||||
->orderBy('sort_order')
|
||||
->get();
|
||||
}
|
||||
|
||||
public static function getJournaledInvoiceIds(int $tenantId, array $invoiceIds): array
|
||||
{
|
||||
return static::where('tenant_id', $tenantId)
|
||||
->whereIn('hometax_invoice_id', $invoiceIds)
|
||||
->distinct()
|
||||
->pluck('hometax_invoice_id')
|
||||
->toArray();
|
||||
}
|
||||
}
|
||||
@@ -2,12 +2,15 @@
|
||||
|
||||
namespace App\Models\Tenants;
|
||||
|
||||
use App\Traits\BelongsToTenant;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
use Illuminate\Support\Facades\Crypt;
|
||||
|
||||
class BarobillSetting extends Model
|
||||
{
|
||||
use BelongsToTenant;
|
||||
|
||||
protected $fillable = [
|
||||
'tenant_id',
|
||||
'corp_num',
|
||||
@@ -23,6 +26,10 @@ class BarobillSetting extends Model
|
||||
'contact_tel',
|
||||
'is_active',
|
||||
'auto_issue',
|
||||
'use_tax_invoice',
|
||||
'use_bank_account',
|
||||
'use_card_usage',
|
||||
'use_hometax',
|
||||
'verified_at',
|
||||
'created_by',
|
||||
'updated_by',
|
||||
@@ -31,6 +38,10 @@ class BarobillSetting extends Model
|
||||
protected $casts = [
|
||||
'is_active' => 'boolean',
|
||||
'auto_issue' => 'boolean',
|
||||
'use_tax_invoice' => 'boolean',
|
||||
'use_bank_account' => 'boolean',
|
||||
'use_card_usage' => 'boolean',
|
||||
'use_hometax' => 'boolean',
|
||||
'verified_at' => 'datetime',
|
||||
];
|
||||
|
||||
@@ -129,4 +140,26 @@ public function getFormattedCorpNumAttribute(): string
|
||||
|
||||
return $num;
|
||||
}
|
||||
|
||||
/**
|
||||
* 활성화된 서비스 목록
|
||||
*/
|
||||
public function getActiveServicesAttribute(): array
|
||||
{
|
||||
$services = [];
|
||||
if ($this->use_tax_invoice) {
|
||||
$services[] = 'tax_invoice';
|
||||
}
|
||||
if ($this->use_bank_account) {
|
||||
$services[] = 'bank_account';
|
||||
}
|
||||
if ($this->use_card_usage) {
|
||||
$services[] = 'card_usage';
|
||||
}
|
||||
if ($this->use_hometax) {
|
||||
$services[] = 'hometax';
|
||||
}
|
||||
|
||||
return $services;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,18 +12,41 @@
|
||||
* 바로빌 API 연동 서비스
|
||||
*
|
||||
* 바로빌 개발자센터: https://dev.barobill.co.kr/
|
||||
* SOAP 서비스 URL: https://ws.baroservice.com/ (운영) / https://testws.baroservice.com/ (테스트)
|
||||
*/
|
||||
class BarobillService extends Service
|
||||
{
|
||||
/**
|
||||
* 바로빌 API 기본 URL
|
||||
* 바로빌 SOAP 서비스 기본 URL (운영)
|
||||
*/
|
||||
private const API_BASE_URL = 'https://ws.barobill.co.kr';
|
||||
private const API_BASE_URL = 'https://ws.baroservice.com';
|
||||
|
||||
/**
|
||||
* 바로빌 API 테스트 URL
|
||||
* 바로빌 SOAP 서비스 테스트 URL
|
||||
*/
|
||||
private const API_TEST_URL = 'https://testws.barobill.co.kr';
|
||||
private const API_TEST_URL = 'https://testws.baroservice.com';
|
||||
|
||||
/**
|
||||
* API 서비스 경로 매핑
|
||||
*/
|
||||
private const SERVICE_PATHS = [
|
||||
'TI' => '/TI.asmx', // 세금계산서
|
||||
'CORPSTATE' => '/CORPSTATE.asmx', // 회원/사업자 관리
|
||||
'BANKACCOUNT' => '/BANKACCOUNT.asmx', // 계좌 조회
|
||||
'CARD' => '/CARD.asmx', // 카드 조회
|
||||
];
|
||||
|
||||
/**
|
||||
* 메서드별 서비스 매핑
|
||||
*/
|
||||
private const METHOD_SERVICE_MAP = [
|
||||
'GetAccessToken' => 'CORPSTATE',
|
||||
'CheckCorpNum' => 'CORPSTATE',
|
||||
'RegistCorp' => 'CORPSTATE',
|
||||
'RegistAndIssueTaxInvoice' => 'TI',
|
||||
'CancelTaxInvoice' => 'TI',
|
||||
'GetTaxInvoiceState' => 'TI',
|
||||
];
|
||||
|
||||
/**
|
||||
* 테스트 모드 여부
|
||||
@@ -32,7 +55,7 @@ class BarobillService extends Service
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->testMode = config('services.barobill.test_mode', true);
|
||||
$this->testMode = (bool) config('services.barobill.test_mode', true);
|
||||
}
|
||||
|
||||
// =========================================================================
|
||||
@@ -44,11 +67,7 @@ public function __construct()
|
||||
*/
|
||||
public function getSetting(): ?BarobillSetting
|
||||
{
|
||||
$tenantId = $this->tenantId();
|
||||
|
||||
return BarobillSetting::query()
|
||||
->where('tenant_id', $tenantId)
|
||||
->first();
|
||||
return BarobillSetting::query()->first();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -59,9 +78,7 @@ public function saveSetting(array $data): BarobillSetting
|
||||
$tenantId = $this->tenantId();
|
||||
$userId = $this->apiUserId();
|
||||
|
||||
$setting = BarobillSetting::query()
|
||||
->where('tenant_id', $tenantId)
|
||||
->first();
|
||||
$setting = BarobillSetting::query()->first();
|
||||
|
||||
if ($setting) {
|
||||
$setting->fill(array_merge($data, ['updated_by' => $userId]));
|
||||
@@ -89,7 +106,6 @@ public function testConnection(): array
|
||||
}
|
||||
|
||||
try {
|
||||
// 바로빌 API 토큰 조회로 연동 테스트
|
||||
$response = $this->callApi('GetAccessToken', [
|
||||
'CERTKEY' => $setting->cert_key,
|
||||
'CorpNum' => $setting->corp_num,
|
||||
@@ -97,7 +113,6 @@ public function testConnection(): array
|
||||
]);
|
||||
|
||||
if (! empty($response['AccessToken'])) {
|
||||
// 검증 성공 시 verified_at 업데이트
|
||||
$setting->verified_at = now();
|
||||
$setting->save();
|
||||
|
||||
@@ -108,7 +123,10 @@ public function testConnection(): array
|
||||
];
|
||||
}
|
||||
|
||||
throw new BadRequestHttpException($response['Message'] ?? __('error.barobill.connection_failed'));
|
||||
return [
|
||||
'success' => false,
|
||||
'message' => $response['Message'] ?? __('error.barobill.connection_failed'),
|
||||
];
|
||||
} catch (\Exception $e) {
|
||||
Log::error('바로빌 연동 테스트 실패', [
|
||||
'tenant_id' => $this->tenantId(),
|
||||
@@ -125,19 +143,11 @@ public function testConnection(): array
|
||||
|
||||
/**
|
||||
* 사업자등록번호 유효성 검사 (휴폐업 조회)
|
||||
*
|
||||
* 바로빌 API를 통해 사업자등록번호의 유효성을 검증합니다.
|
||||
* 바로빌 설정이 없는 경우 기본 형식 검증만 수행합니다.
|
||||
*
|
||||
* @param string $businessNumber 사업자등록번호 (10자리, 하이픈 제거)
|
||||
* @return array{valid: bool, status: string, status_label: string, corp_name: ?string, ceo_name: ?string, message: string}
|
||||
*/
|
||||
public function checkBusinessNumber(string $businessNumber): array
|
||||
{
|
||||
// 하이픈 제거 및 숫자만 추출
|
||||
$businessNumber = preg_replace('/[^0-9]/', '', $businessNumber);
|
||||
|
||||
// 기본 형식 검증 (10자리)
|
||||
if (strlen($businessNumber) !== 10) {
|
||||
return [
|
||||
'valid' => false,
|
||||
@@ -149,7 +159,6 @@ public function checkBusinessNumber(string $businessNumber): array
|
||||
];
|
||||
}
|
||||
|
||||
// 체크섬 검증 (사업자등록번호 자체 유효성)
|
||||
if (! $this->validateBusinessNumberChecksum($businessNumber)) {
|
||||
return [
|
||||
'valid' => false,
|
||||
@@ -161,16 +170,14 @@ public function checkBusinessNumber(string $businessNumber): array
|
||||
];
|
||||
}
|
||||
|
||||
// 바로빌 API 조회 시도
|
||||
try {
|
||||
$response = $this->callApi('CheckCorpNum', [
|
||||
'CorpNum' => $businessNumber,
|
||||
]);
|
||||
|
||||
// 바로빌 응답 해석
|
||||
if (isset($response['CorpState'])) {
|
||||
$state = $response['CorpState'];
|
||||
$isValid = in_array($state, ['01', '02']); // 01: 사업중, 02: 휴업
|
||||
$isValid = in_array($state, ['01', '02']);
|
||||
$statusLabel = match ($state) {
|
||||
'01' => '사업중',
|
||||
'02' => '휴업',
|
||||
@@ -190,7 +197,6 @@ public function checkBusinessNumber(string $businessNumber): array
|
||||
];
|
||||
}
|
||||
|
||||
// 응답 형식이 다른 경우 (결과 코드 방식)
|
||||
if (isset($response['Result'])) {
|
||||
$isValid = $response['Result'] >= 0;
|
||||
|
||||
@@ -206,7 +212,6 @@ public function checkBusinessNumber(string $businessNumber): array
|
||||
];
|
||||
}
|
||||
|
||||
// 기본 응답 (체크섬만 통과한 경우)
|
||||
return [
|
||||
'valid' => true,
|
||||
'status' => 'format_valid',
|
||||
@@ -216,7 +221,6 @@ public function checkBusinessNumber(string $businessNumber): array
|
||||
'message' => __('message.company.business_number_format_valid'),
|
||||
];
|
||||
} catch (\Exception $e) {
|
||||
// API 호출 실패 시 형식 검증 결과만 반환
|
||||
Log::warning('바로빌 사업자번호 조회 실패', [
|
||||
'business_number' => $businessNumber,
|
||||
'error' => $e->getMessage(),
|
||||
@@ -235,8 +239,6 @@ public function checkBusinessNumber(string $businessNumber): array
|
||||
|
||||
/**
|
||||
* 사업자등록번호 체크섬 검증
|
||||
*
|
||||
* @param string $businessNumber 10자리 사업자등록번호
|
||||
*/
|
||||
private function validateBusinessNumberChecksum(string $businessNumber): bool
|
||||
{
|
||||
@@ -252,7 +254,6 @@ private function validateBusinessNumberChecksum(string $businessNumber): bool
|
||||
$sum += intval($digits[$i]) * $multipliers[$i];
|
||||
}
|
||||
|
||||
// 8번째 자리 (인덱스 8)에 대한 추가 처리
|
||||
$sum += intval(floor(intval($digits[8]) * 5 / 10));
|
||||
|
||||
$remainder = $sum % 10;
|
||||
@@ -277,14 +278,11 @@ public function issueTaxInvoice(TaxInvoice $taxInvoice): TaxInvoice
|
||||
}
|
||||
|
||||
try {
|
||||
// 바로빌 API 호출을 위한 데이터 구성
|
||||
$apiData = $this->buildTaxInvoiceData($taxInvoice, $setting);
|
||||
|
||||
// 세금계산서 발행 API 호출
|
||||
$response = $this->callApi('RegistAndIssueTaxInvoice', $apiData);
|
||||
|
||||
if (! empty($response['InvoiceID'])) {
|
||||
// 발행 성공
|
||||
$taxInvoice->barobill_invoice_id = $response['InvoiceID'];
|
||||
$taxInvoice->nts_confirm_num = $response['NTSConfirmNum'] ?? null;
|
||||
$taxInvoice->status = TaxInvoice::STATUS_ISSUED;
|
||||
@@ -301,9 +299,10 @@ public function issueTaxInvoice(TaxInvoice $taxInvoice): TaxInvoice
|
||||
return $taxInvoice->fresh();
|
||||
}
|
||||
|
||||
throw new \Exception($response['Message'] ?? '발행 실패');
|
||||
throw new \RuntimeException($response['Message'] ?? '발행 실패');
|
||||
} catch (BadRequestHttpException $e) {
|
||||
throw $e;
|
||||
} catch (\Exception $e) {
|
||||
// 발행 실패
|
||||
$taxInvoice->status = TaxInvoice::STATUS_FAILED;
|
||||
$taxInvoice->error_message = $e->getMessage();
|
||||
$taxInvoice->save();
|
||||
@@ -334,7 +333,6 @@ public function cancelTaxInvoice(TaxInvoice $taxInvoice, string $reason): TaxInv
|
||||
}
|
||||
|
||||
try {
|
||||
// 세금계산서 취소 API 호출
|
||||
$response = $this->callApi('CancelTaxInvoice', [
|
||||
'CERTKEY' => $setting->cert_key,
|
||||
'CorpNum' => $setting->corp_num,
|
||||
@@ -358,7 +356,9 @@ public function cancelTaxInvoice(TaxInvoice $taxInvoice, string $reason): TaxInv
|
||||
return $taxInvoice->fresh();
|
||||
}
|
||||
|
||||
throw new \Exception($response['Message'] ?? '취소 실패');
|
||||
throw new \RuntimeException($response['Message'] ?? '취소 실패');
|
||||
} catch (BadRequestHttpException $e) {
|
||||
throw $e;
|
||||
} catch (\Exception $e) {
|
||||
Log::error('세금계산서 취소 실패', [
|
||||
'tenant_id' => $this->tenantId(),
|
||||
@@ -396,7 +396,6 @@ public function checkNtsSendStatus(TaxInvoice $taxInvoice): TaxInvoice
|
||||
if (! empty($response['State'])) {
|
||||
$taxInvoice->nts_send_status = $response['State'];
|
||||
|
||||
// 국세청 전송 완료 시 상태 업데이트
|
||||
if ($response['State'] === '전송완료' && ! $taxInvoice->sent_at) {
|
||||
$taxInvoice->status = TaxInvoice::STATUS_SENT;
|
||||
$taxInvoice->sent_at = now();
|
||||
@@ -418,6 +417,26 @@ public function checkNtsSendStatus(TaxInvoice $taxInvoice): TaxInvoice
|
||||
}
|
||||
}
|
||||
|
||||
// =========================================================================
|
||||
// URL 헬퍼
|
||||
// =========================================================================
|
||||
|
||||
/**
|
||||
* 바로빌 API base URL 반환
|
||||
*/
|
||||
public function getBaseUrl(): string
|
||||
{
|
||||
return $this->testMode ? self::API_TEST_URL : self::API_BASE_URL;
|
||||
}
|
||||
|
||||
/**
|
||||
* 테스트 모드 여부
|
||||
*/
|
||||
public function isTestMode(): bool
|
||||
{
|
||||
return $this->testMode;
|
||||
}
|
||||
|
||||
// =========================================================================
|
||||
// Private 메서드
|
||||
// =========================================================================
|
||||
@@ -427,8 +446,10 @@ public function checkNtsSendStatus(TaxInvoice $taxInvoice): TaxInvoice
|
||||
*/
|
||||
private function callApi(string $method, array $data): array
|
||||
{
|
||||
$baseUrl = $this->testMode ? self::API_TEST_URL : self::API_BASE_URL;
|
||||
$url = $baseUrl.'/TI/'.$method;
|
||||
$baseUrl = $this->getBaseUrl();
|
||||
$servicePath = self::METHOD_SERVICE_MAP[$method] ?? 'TI';
|
||||
$path = self::SERVICE_PATHS[$servicePath] ?? '/TI.asmx';
|
||||
$url = $baseUrl.$path.'/'.$method;
|
||||
|
||||
$response = Http::timeout(30)
|
||||
->withHeaders([
|
||||
@@ -437,7 +458,7 @@ private function callApi(string $method, array $data): array
|
||||
->post($url, $data);
|
||||
|
||||
if ($response->failed()) {
|
||||
throw new \Exception('API 호출 실패: '.$response->status());
|
||||
throw new \RuntimeException('API 호출 실패: '.$response->status());
|
||||
}
|
||||
|
||||
return $response->json() ?? [];
|
||||
@@ -448,7 +469,6 @@ private function callApi(string $method, array $data): array
|
||||
*/
|
||||
private function buildTaxInvoiceData(TaxInvoice $taxInvoice, BarobillSetting $setting): array
|
||||
{
|
||||
// 품목 데이터 구성
|
||||
$items = [];
|
||||
foreach ($taxInvoice->items ?? [] as $index => $item) {
|
||||
$items[] = [
|
||||
@@ -463,7 +483,6 @@ private function buildTaxInvoiceData(TaxInvoice $taxInvoice, BarobillSetting $se
|
||||
];
|
||||
}
|
||||
|
||||
// 품목이 없는 경우 기본 품목 추가
|
||||
if (empty($items)) {
|
||||
$items[] = [
|
||||
'PurchaseDT' => $taxInvoice->issue_date->format('Ymd'),
|
||||
@@ -487,8 +506,6 @@ private function buildTaxInvoiceData(TaxInvoice $taxInvoice, BarobillSetting $se
|
||||
'TaxType' => '과세',
|
||||
'PurposeType' => '영수',
|
||||
'WriteDate' => $taxInvoice->issue_date->format('Ymd'),
|
||||
|
||||
// 공급자 정보
|
||||
'InvoicerCorpNum' => $taxInvoice->supplier_corp_num,
|
||||
'InvoicerCorpName' => $taxInvoice->supplier_corp_name,
|
||||
'InvoicerCEOName' => $taxInvoice->supplier_ceo_name,
|
||||
@@ -496,8 +513,6 @@ private function buildTaxInvoiceData(TaxInvoice $taxInvoice, BarobillSetting $se
|
||||
'InvoicerBizType' => $taxInvoice->supplier_biz_type,
|
||||
'InvoicerBizClass' => $taxInvoice->supplier_biz_class,
|
||||
'InvoicerContactID' => $taxInvoice->supplier_contact_id,
|
||||
|
||||
// 공급받는자 정보
|
||||
'InvoiceeCorpNum' => $taxInvoice->buyer_corp_num,
|
||||
'InvoiceeCorpName' => $taxInvoice->buyer_corp_name,
|
||||
'InvoiceeCEOName' => $taxInvoice->buyer_ceo_name,
|
||||
@@ -505,16 +520,10 @@ private function buildTaxInvoiceData(TaxInvoice $taxInvoice, BarobillSetting $se
|
||||
'InvoiceeBizType' => $taxInvoice->buyer_biz_type,
|
||||
'InvoiceeBizClass' => $taxInvoice->buyer_biz_class,
|
||||
'InvoiceeContactID' => $taxInvoice->buyer_contact_id,
|
||||
|
||||
// 금액 정보
|
||||
'SupplyCostTotal' => (int) $taxInvoice->supply_amount,
|
||||
'TaxTotal' => (int) $taxInvoice->tax_amount,
|
||||
'TotalAmount' => (int) $taxInvoice->total_amount,
|
||||
|
||||
// 품목 정보
|
||||
'TaxInvoiceTradeLineItems' => $items,
|
||||
|
||||
// 비고
|
||||
'Remark1' => $taxInvoice->description ?? '',
|
||||
],
|
||||
];
|
||||
|
||||
@@ -58,4 +58,17 @@
|
||||
'exchange_secret' => env('INTERNAL_EXCHANGE_SECRET'),
|
||||
],
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| BaroBill (바로빌 전자세금계산서/회계 연동)
|
||||
|--------------------------------------------------------------------------
|
||||
| MNG와 동일한 설정 구조를 사용한다.
|
||||
*/
|
||||
'barobill' => [
|
||||
'cert_key_test' => env('BAROBILL_CERT_KEY_TEST', ''),
|
||||
'cert_key_prod' => env('BAROBILL_CERT_KEY_PROD', ''),
|
||||
'corp_num' => env('BAROBILL_CORP_NUM', ''),
|
||||
'test_mode' => env('BAROBILL_TEST_MODE', true),
|
||||
],
|
||||
|
||||
];
|
||||
|
||||
@@ -0,0 +1,59 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
/**
|
||||
* 바로빌 관련 테이블에 options JSON 컬럼 추가
|
||||
*
|
||||
* SAM options 컬럼 정책에 따라 모든 비즈니스 테이블에
|
||||
* 확장 가능한 options JSON 컬럼을 추가한다.
|
||||
*
|
||||
* @see docs/standards/options-column-policy.md
|
||||
*/
|
||||
return new class extends Migration
|
||||
{
|
||||
/**
|
||||
* options 컬럼을 추가할 테이블 목록
|
||||
*/
|
||||
private array $tables = [
|
||||
'barobill_settings',
|
||||
'barobill_configs',
|
||||
'barobill_members',
|
||||
'barobill_subscriptions',
|
||||
'barobill_billing_records',
|
||||
'barobill_monthly_summaries',
|
||||
'barobill_pricing_policies',
|
||||
'barobill_bank_transactions',
|
||||
'barobill_bank_transaction_overrides',
|
||||
'barobill_bank_transaction_splits',
|
||||
'barobill_bank_sync_status',
|
||||
'barobill_card_transactions',
|
||||
'barobill_card_transaction_splits',
|
||||
'barobill_card_transaction_amount_logs',
|
||||
'barobill_card_transaction_hides',
|
||||
];
|
||||
|
||||
public function up(): void
|
||||
{
|
||||
foreach ($this->tables as $table) {
|
||||
if (Schema::hasTable($table) && ! Schema::hasColumn($table, 'options')) {
|
||||
Schema::table($table, function (Blueprint $table) {
|
||||
$table->json('options')->nullable()->after('id');
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function down(): void
|
||||
{
|
||||
foreach ($this->tables as $table) {
|
||||
if (Schema::hasTable($table) && Schema::hasColumn($table, 'options')) {
|
||||
Schema::table($table, function (Blueprint $table) {
|
||||
$table->dropColumn('options');
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
Reference in New Issue
Block a user