'대기', self::STATUS_COMPLETED => '완료', self::STATUS_FAILED => '실패', self::STATUS_CANCELLED => '취소', self::STATUS_REFUNDED => '환불', ]; /** 결제 수단 */ public const METHOD_CARD = 'card'; public const METHOD_BANK = 'bank'; public const METHOD_VIRTUAL = 'virtual'; public const METHOD_CASH = 'cash'; public const METHOD_FREE = 'free'; public const PAYMENT_METHODS = [ self::METHOD_CARD, self::METHOD_BANK, self::METHOD_VIRTUAL, self::METHOD_CASH, self::METHOD_FREE, ]; /** 결제 수단 라벨 */ public const METHOD_LABELS = [ self::METHOD_CARD => '카드', self::METHOD_BANK => '계좌이체', self::METHOD_VIRTUAL => '가상계좌', self::METHOD_CASH => '현금', self::METHOD_FREE => '무료', ]; // ========================================================================= // 모델 설정 // ========================================================================= protected $fillable = [ 'subscription_id', 'amount', 'payment_method', 'transaction_id', 'paid_at', 'status', 'memo', 'created_by', 'updated_by', 'deleted_by', ]; protected $casts = [ 'amount' => 'float', 'paid_at' => 'datetime', ]; protected $attributes = [ 'status' => self::STATUS_PENDING, 'payment_method' => self::METHOD_CARD, ]; // ========================================================================= // 스코프 // ========================================================================= /** * 완료된 결제만 */ public function scopeCompleted(Builder $query): Builder { return $query->where('status', self::STATUS_COMPLETED); } /** * 특정 상태 */ public function scopeOfStatus(Builder $query, string $status): Builder { return $query->where('status', $status); } /** * 특정 결제 수단 */ public function scopeOfMethod(Builder $query, string $method): Builder { return $query->where('payment_method', $method); } /** * 기간별 필터 */ public function scopeBetweenDates(Builder $query, ?string $startDate, ?string $endDate): Builder { if ($startDate) { $query->where('paid_at', '>=', $startDate); } if ($endDate) { $query->where('paid_at', '<=', $endDate.' 23:59:59'); } return $query; } // ========================================================================= // 관계 // ========================================================================= public function subscription(): BelongsTo { return $this->belongsTo(Subscription::class); } // ========================================================================= // 접근자 // ========================================================================= /** * 상태 라벨 */ public function getStatusLabelAttribute(): string { return self::STATUS_LABELS[$this->status] ?? $this->status; } /** * 결제 수단 라벨 */ public function getPaymentMethodLabelAttribute(): string { return self::METHOD_LABELS[$this->payment_method] ?? $this->payment_method; } /** * 포맷된 금액 */ public function getFormattedAmountAttribute(): string { return number_format($this->amount).'원'; } /** * 결제 완료 여부 */ public function getIsCompletedAttribute(): bool { return $this->status === self::STATUS_COMPLETED; } /** * 환불 가능 여부 */ public function getIsRefundableAttribute(): bool { return $this->status === self::STATUS_COMPLETED; } // ========================================================================= // 헬퍼 메서드 // ========================================================================= /** * 결제 완료 처리 */ public function complete(?string $transactionId = null): bool { if ($this->status !== self::STATUS_PENDING) { return false; } $this->status = self::STATUS_COMPLETED; $this->paid_at = now(); if ($transactionId) { $this->transaction_id = $transactionId; } return $this->save(); } /** * 결제 실패 처리 */ public function fail(?string $reason = null): bool { if ($this->status !== self::STATUS_PENDING) { return false; } $this->status = self::STATUS_FAILED; if ($reason) { $this->memo = $reason; } return $this->save(); } /** * 결제 취소 처리 */ public function cancel(?string $reason = null): bool { if (! in_array($this->status, [self::STATUS_PENDING, self::STATUS_COMPLETED])) { return false; } $this->status = self::STATUS_CANCELLED; if ($reason) { $this->memo = $reason; } return $this->save(); } /** * 환불 처리 */ public function refund(?string $reason = null): bool { if ($this->status !== self::STATUS_COMPLETED) { return false; } $this->status = self::STATUS_REFUNDED; if ($reason) { $this->memo = $reason; } return $this->save(); } }