LEVEL 56 QUEST
환불/취소 & 정산 관리
전액/부분 환불 처리, 매출 정산 리포트 생성, 세금계산서 발행까지 결제 시스템의 마지막 퍼즐을 완성합니다.
환불 처리 로직
환불의 종류
SaaS 서비스에서 환불은 크게 두 가지로 나뉩니다. 전액 환불은 결제 금액 전체를 돌려주는 것이고, 부분 환불은 사용한 기간을 제외한 나머지 금액만 돌려줍니다. 전자상거래법에 따라 구매 후 7일 이내 청약철회 시 전액 환불이 원칙입니다.
| 환불 유형 | 적용 조건 | 환불 금액 |
|---|---|---|
| 전액 환불 | 결제 후 7일 이내 (청약철회) | 결제 금액 100% |
| 부분 환불 (일할 계산) | 7일 이후 ~ 결제 주기 내 | 잔여 기간 비례 금액 |
| 환불 불가 | 이미 전액 소비된 서비스 | 0원 (사유 안내) |
// classes/RefundService.php - 환불 처리 서비스
<?php
class RefundService {
private PDO $pdo;
private TossBillingService $toss;
private PayPalClient $paypal;
public function __construct(PDO $pdo, array $config) {
$this->pdo = $pdo;
$this->toss = new TossBillingService($config['toss']['secret_key']);
$this->paypal = new PayPalClient($config['paypal']);
}
/**
* 환불 금액 계산
*/
public function calculateRefundAmount(array $payment): array {
$approvedAt = new DateTime($payment['approved_at']);
$now = new DateTime();
$daysSincePurchase = $approvedAt->diff($now)->days;
// 7일 이내: 전액 환불 (청약철회)
if ($daysSincePurchase 7) {
return [
'type' => 'full',
'amount' => $payment['amount'],
'reason' => '청약철회 (7일 이내)',
];
}
// 7일 이후: 잔여 기간 일할 계산
$totalDays = 30; // 월간 구독 기준
$usedDays = min($daysSincePurchase, $totalDays);
$remainingDays = $totalDays - $usedDays;
$refundAmount = (int)floor(
$payment['amount'] * ($remainingDays / $totalDays)
);
return [
'type' => 'partial',
'amount' => max(0, $refundAmount),
'used_days' => $usedDays,
'remain_days' => $remainingDays,
'reason' => "부분 환불 (사용 {$usedDays}일, 잔여 {$remainingDays}일)",
];
}
/**
* 환불 실행 (토스 또는 PayPal 자동 분기)
*/
public function processRefund(
int $paymentId,
int $refundAmount,
string $reason
): array {
$stmt = $this->pdo->prepare("SELECT * FROM payments WHERE id = ?");
$stmt->execute([$paymentId]);
$payment = $stmt->fetch();
if (!$payment || $payment['status'] !== 'approved') {
return ['success' => false, 'error' => '환불 대상 결제를 찾을 수 없습니다'];
}
if ($refundAmount > $payment['amount']) {
return ['success' => false, 'error' => '환불 금액이 결제 금액을 초과합니다'];
}
// 결제 수단에 따라 분기
if ($payment['method'] === 'paypal') {
return $this->refundPayPal($payment, $refundAmount, $reason);
} else {
return $this->refundToss($payment, $refundAmount, $reason);
}
}
}
토스페이먼츠 환불 API 연동
// RefundService - 토스페이먼츠 환불 메서드
private function refundToss(
array $payment,
int $refundAmount,
string $reason
): array {
$config = require 'config/payment.php';
$secretKey = $config['toss']['secret_key'];
$paymentKey = $payment['payment_key'];
// 토스 환불 API 호출
$ch = curl_init();
curl_setopt_array($ch, [
CURLOPT_URL => "https://api.tosspayments.com/v1/payments/{$paymentKey}/cancel",
CURLOPT_POST => true,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
'Authorization: Basic ' . base64_encode($secretKey . ':'),
'Content-Type: application/json',
],
CURLOPT_POSTFIELDS => json_encode([
'cancelReason' => $reason,
'cancelAmount' => $refundAmount, // 부분 환불 시 금액 지정
]),
]);
$response = json_decode(curl_exec($ch), true);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
if ($httpCode === 200) {
// 환불 성공 → DB 업데이트
$this->recordRefund($payment, $refundAmount, $reason, 'toss');
return ['success' => true, 'refund_amount' => $refundAmount];
}
return [
'success' => false,
'error' => $response['message'] ?? '토스 환불 API 오류',
];
}
🔒
여기까지는 미리보기입니다
환불/취소 & 정산 관리
무료 가입하면 이어서 볼 수 있고, 강의를 완료할 때마다 XP와 레벨이 쌓입니다.
Google로 3초 만에 시작 →🧵 Threads로 시작무료 공개 강의 둘러보기 (Lv.1~3)무료 가입하면 이어서 볼 수 있고, 강의를 완료할 때마다 XP와 레벨이 쌓입니다.