Project
— Overview
고도몰(Godo Mall) 플랫폼에서 운영하던 공동구매 모듈을 CodeIgniter 4 기반 독립 웹앱으로 재구현한 프로젝트. 바스켓(공구) 단위로 상품을 묶어 신청자가 목표 수량을 달성하면 일괄 결제가 진행되는 방식으로, 싱글·멀티 바스켓 유형과 9단계 진행 상태 머신을 포함한다.
사용자 화면(목록·상세·신청), 관리자 화면(공구 등록·상태 전환·신청자 관리), NicePay PG 연동 구조, SMS 발송 로직, Cron 배치를 MVC 레이어로 분리해 구현했다.
— Core Logic
공구의 진행 상태는 9단계 상수로 관리되며, GroupBuyService가
허용된 전환만 실행한다. 신청 시에는 바스켓 상태 확인과 중복 신청 방지를
서비스 레이어에서 처리하고, DB의 UNIQUE 제약으로 이중 방어한다.
// GroupBuyService::apply() — 신청 유효성 검사
public function apply(array $basket, int $memNo): array
{
if ($basket['progress'] !== self::STATUS_IN_PROGRESS) {
return ['success' => false, 'message' => '신청 가능한 공구가 아닙니다.'];
}
$existing = $this->applyModel->findActive($basket['code'], $memNo);
if ($existing) {
return ['success' => false, 'message' => '이미 신청한 공구입니다.'];
}
$this->applyModel->insert([...]);
$this->basketModel
->set('current_qty', 'current_qty + 1', false)
->where('code', $basket['code'])
->update();
return ['success' => true, 'message' => '신청이 완료되었습니다.'];
}app/Services/GroupBuyService.php — apply with validation
diy_price(공구 전용 할인가) 등록 시에는 관리자가 설정한 최대 할인율을 초과하지 않는지 검증한다. 실시간으로 계산해 거부 메시지에 실제 할인율을 포함한다.
// 할인율 상한 검증
public function validateDiyPrice(int $goodsPrice, int $diyPrice, int $maxRate): array
{
$rate = ($goodsPrice - $diyPrice) / $goodsPrice * 100;
if ($rate > $maxRate) {
return [
'valid' => false,
'message' => "최대 할인율({$maxRate}%)을 초과했습니다. (현재 " . round($rate, 1) . "%)",
];
}
return ['valid' => true];
}app/Services/GroupBuyService.php — discount rate validation
— How It Works
01 시작대기)03 진행중으로 전환되고 기존 신청자에게 SMS가 발송된다.gb_applies에 레코드가 생성되고 current_qty가 증가한다. UNIQUE 제약으로 중복 신청을 방지한다.php spark basket:batch)가 마감된 공구를 순회해 목표 달성 여부에 따라 07 결제대기 또는 06 미달성실패로 전환한다.08 결제성공으로 최종 완료된다.— Mock Services
실제 PG·SMS 연동 없이 구조와 데이터 흐름을 시현한다.
// NicePayMock — PG 연동 구조 시현 NicePayMock::requestPayment($basketCode, $amount, $cardNo) // → ['status' => 'success', 'tid' => 'mock_GB20240101001_1704067200'] // SmsMock — writable/logs/sms.log 에 기록 SmsMock::send($email, 'basket_complete', ['goods' => '제주 감귤 5kg']) // [2024-01-01 12:00:00] TO:user@demo.com TMPL:basket_complete MSG:[공동구매] 목표 달성! // Cron 배치 — CI4 Spark 커맨드 // $ php spark basket:batch // [달성] GB20240101002 → 결제대기(07) // [미달성] GB20240101003 → 미달성실패(06)
app/Services/Mock/ — NicePayMock, SmsMock · app/Commands/BasketStatusBatch.php