Files
sam-manage/app/Services/Barobill/BarobillBillingService.php
pro 39161d1203 feat:바로빌 과금관리 시스템 구현
- 모델: BarobillSubscription, BarobillBillingRecord, BarobillMonthlySummary
- 서비스: BarobillBillingService (구독/과금 처리 로직)
- API 컨트롤러: BarobillBillingController (구독/과금 CRUD)
- 뷰: 과금 현황 탭, 구독 관리 탭, 통계 카드, 상세 모달
- 라우트: 웹/API 라우트 추가

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-27 15:03:44 +09:00

253 lines
8.3 KiB
PHP

<?php
namespace App\Services\Barobill;
use App\Models\Barobill\BarobillBillingRecord;
use App\Models\Barobill\BarobillMember;
use App\Models\Barobill\BarobillMonthlySummary;
use App\Models\Barobill\BarobillSubscription;
use Carbon\Carbon;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Log;
/**
* 바로빌 과금 서비스
*
* 월정액 구독 관리 및 과금 처리
*/
class BarobillBillingService
{
/**
* 회원사의 구독 목록 조회
*/
public function getSubscriptions(int $memberId): array
{
return BarobillSubscription::where('member_id', $memberId)
->orderBy('service_type')
->get()
->toArray();
}
/**
* 구독 등록/수정
*/
public function saveSubscription(int $memberId, string $serviceType, array $data): BarobillSubscription
{
return BarobillSubscription::updateOrCreate(
[
'member_id' => $memberId,
'service_type' => $serviceType,
],
[
'monthly_fee' => $data['monthly_fee'] ?? BarobillSubscription::DEFAULT_MONTHLY_FEES[$serviceType] ?? 0,
'started_at' => $data['started_at'] ?? now()->toDateString(),
'ended_at' => $data['ended_at'] ?? null,
'is_active' => $data['is_active'] ?? true,
'memo' => $data['memo'] ?? null,
]
);
}
/**
* 구독 해지
*/
public function cancelSubscription(int $subscriptionId): bool
{
$subscription = BarobillSubscription::find($subscriptionId);
if (!$subscription) {
return false;
}
$subscription->update([
'ended_at' => now()->toDateString(),
'is_active' => false,
]);
return true;
}
/**
* 월별 과금 처리 (배치용)
*
* 매월 1일에 실행하여 전월 구독료 과금
*/
public function processMonthlyBilling(?string $billingMonth = null): array
{
$billingMonth = $billingMonth ?? now()->format('Y-m');
$billedAt = Carbon::parse($billingMonth . '-01');
$results = [
'billing_month' => $billingMonth,
'processed' => 0,
'skipped' => 0,
'errors' => [],
];
// 활성 구독 조회
$subscriptions = BarobillSubscription::active()
->with('member')
->get();
foreach ($subscriptions as $subscription) {
try {
// 이미 과금된 기록이 있는지 확인
$exists = BarobillBillingRecord::where('member_id', $subscription->member_id)
->where('billing_month', $billingMonth)
->where('service_type', $subscription->service_type)
->where('billing_type', 'subscription')
->exists();
if ($exists) {
$results['skipped']++;
continue;
}
// 과금 기록 생성
BarobillBillingRecord::create([
'member_id' => $subscription->member_id,
'billing_month' => $billingMonth,
'service_type' => $subscription->service_type,
'billing_type' => 'subscription',
'quantity' => 1,
'unit_price' => $subscription->monthly_fee,
'total_amount' => $subscription->monthly_fee,
'billed_at' => $billedAt,
'description' => BarobillSubscription::SERVICE_TYPES[$subscription->service_type] . ' 월정액',
]);
$results['processed']++;
} catch (\Exception $e) {
$results['errors'][] = [
'member_id' => $subscription->member_id,
'service_type' => $subscription->service_type,
'error' => $e->getMessage(),
];
Log::error('월정액 과금 처리 실패', [
'subscription_id' => $subscription->id,
'error' => $e->getMessage(),
]);
}
}
// 월별 집계 갱신
$this->updateMonthlySummaries($billingMonth);
return $results;
}
/**
* 건별 사용량 과금 (세금계산서)
*/
public function recordUsage(int $memberId, string $serviceType, int $quantity, ?string $billingMonth = null): BarobillBillingRecord
{
$billingMonth = $billingMonth ?? now()->format('Y-m');
$unitPrice = BarobillBillingRecord::USAGE_UNIT_PRICES[$serviceType] ?? 0;
$record = BarobillBillingRecord::updateOrCreate(
[
'member_id' => $memberId,
'billing_month' => $billingMonth,
'service_type' => $serviceType,
'billing_type' => 'usage',
],
[
'quantity' => $quantity,
'unit_price' => $unitPrice,
'total_amount' => $quantity * $unitPrice,
'billed_at' => now()->toDateString(),
'description' => BarobillBillingRecord::SERVICE_TYPES[$serviceType] . ' ' . $quantity . '건',
]
);
// 집계 갱신
BarobillMonthlySummary::updateOrCreateSummary($memberId, $billingMonth);
return $record;
}
/**
* 월별 집계 갱신
*/
public function updateMonthlySummaries(string $billingMonth): void
{
// 해당 월에 과금 기록이 있는 회원사 조회
$memberIds = BarobillBillingRecord::where('billing_month', $billingMonth)
->distinct()
->pluck('member_id');
foreach ($memberIds as $memberId) {
BarobillMonthlySummary::updateOrCreateSummary($memberId, $billingMonth);
}
}
/**
* 월별 과금 현황 조회
*/
public function getMonthlyBillingList(string $billingMonth, ?int $tenantId = null): array
{
$query = BarobillMonthlySummary::with(['member.tenant'])
->where('billing_month', $billingMonth);
if ($tenantId) {
$query->whereHas('member', function ($q) use ($tenantId) {
$q->where('tenant_id', $tenantId);
});
}
return $query->orderBy('grand_total', 'desc')->get()->toArray();
}
/**
* 월별 합계 조회
*/
public function getMonthlyTotal(string $billingMonth, ?int $tenantId = null): array
{
$query = BarobillMonthlySummary::where('billing_month', $billingMonth);
if ($tenantId) {
$query->whereHas('member', function ($q) use ($tenantId) {
$q->where('tenant_id', $tenantId);
});
}
$result = $query->selectRaw('
COUNT(*) as member_count,
SUM(bank_account_fee) as bank_account_fee,
SUM(card_fee) as card_fee,
SUM(hometax_fee) as hometax_fee,
SUM(subscription_total) as subscription_total,
SUM(tax_invoice_count) as tax_invoice_count,
SUM(tax_invoice_amount) as tax_invoice_amount,
SUM(usage_total) as usage_total,
SUM(grand_total) as grand_total
')->first();
return [
'billing_month' => $billingMonth,
'member_count' => (int) ($result->member_count ?? 0),
'bank_account_fee' => (int) ($result->bank_account_fee ?? 0),
'card_fee' => (int) ($result->card_fee ?? 0),
'hometax_fee' => (int) ($result->hometax_fee ?? 0),
'subscription_total' => (int) ($result->subscription_total ?? 0),
'tax_invoice_count' => (int) ($result->tax_invoice_count ?? 0),
'tax_invoice_amount' => (int) ($result->tax_invoice_amount ?? 0),
'usage_total' => (int) ($result->usage_total ?? 0),
'grand_total' => (int) ($result->grand_total ?? 0),
];
}
/**
* 연간 과금 추이 조회
*/
public function getYearlyTrend(int $year, ?int $tenantId = null): array
{
$months = [];
for ($m = 1; $m <= 12; $m++) {
$billingMonth = sprintf('%d-%02d', $year, $m);
$months[$billingMonth] = $this->getMonthlyTotal($billingMonth, $tenantId);
}
return $months;
}
}