feat: 개인정보 수집·이용 동의 프로세스 추가
- QR 라벨에 개인정보 동의 안내 문구 추가 (18pt 작은 글씨) - 웹앱에 핀테크 스타일 개인정보 동의 체크박스 추가 - 백엔드 API에서 개인정보 동의 검증 추가 - 개인정보보호법 준수 강화 Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
d2ad64ebd8
commit
1717f4c6c2
@ -276,6 +276,7 @@ def api_claim():
|
|||||||
nonce = data.get('nonce')
|
nonce = data.get('nonce')
|
||||||
phone = data.get('phone', '').strip()
|
phone = data.get('phone', '').strip()
|
||||||
name = data.get('name', '').strip()
|
name = data.get('name', '').strip()
|
||||||
|
privacy_consent = data.get('privacy_consent', False)
|
||||||
|
|
||||||
# 입력 검증
|
# 입력 검증
|
||||||
if not phone or not name:
|
if not phone or not name:
|
||||||
@ -284,6 +285,13 @@ def api_claim():
|
|||||||
'message': '전화번호와 이름을 모두 입력해주세요.'
|
'message': '전화번호와 이름을 모두 입력해주세요.'
|
||||||
}), 400
|
}), 400
|
||||||
|
|
||||||
|
# 개인정보 동의 검증
|
||||||
|
if not privacy_consent:
|
||||||
|
return jsonify({
|
||||||
|
'success': False,
|
||||||
|
'message': '개인정보 수집·이용에 동의해주세요.'
|
||||||
|
}), 400
|
||||||
|
|
||||||
# 전화번호 형식 정리 (하이픈 제거)
|
# 전화번호 형식 정리 (하이픈 제거)
|
||||||
phone = phone.replace('-', '').replace(' ', '')
|
phone = phone.replace('-', '').replace(' ', '')
|
||||||
|
|
||||||
|
|||||||
@ -166,6 +166,72 @@
|
|||||||
font-weight: 400;
|
font-weight: 400;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.privacy-consent {
|
||||||
|
margin: 24px 0 8px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.checkbox-container {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
cursor: pointer;
|
||||||
|
user-select: none;
|
||||||
|
padding: 4px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.checkbox-container input[type="checkbox"] {
|
||||||
|
position: absolute;
|
||||||
|
opacity: 0;
|
||||||
|
cursor: pointer;
|
||||||
|
height: 0;
|
||||||
|
width: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.checkmark {
|
||||||
|
position: relative;
|
||||||
|
height: 24px;
|
||||||
|
width: 24px;
|
||||||
|
background-color: #f8f9fa;
|
||||||
|
border: 2px solid #e9ecef;
|
||||||
|
border-radius: 6px;
|
||||||
|
transition: all 0.2s ease;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.checkbox-container:hover .checkmark {
|
||||||
|
border-color: #6366f1;
|
||||||
|
background-color: #ffffff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.checkbox-container input:checked ~ .checkmark {
|
||||||
|
background: linear-gradient(135deg, #6366f1 0%, #8b5cf6 100%);
|
||||||
|
border-color: #6366f1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.checkmark::after {
|
||||||
|
content: "";
|
||||||
|
position: absolute;
|
||||||
|
display: none;
|
||||||
|
left: 7px;
|
||||||
|
top: 3px;
|
||||||
|
width: 6px;
|
||||||
|
height: 11px;
|
||||||
|
border: solid white;
|
||||||
|
border-width: 0 2px 2px 0;
|
||||||
|
transform: rotate(45deg);
|
||||||
|
}
|
||||||
|
|
||||||
|
.checkbox-container input:checked ~ .checkmark::after {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.consent-text {
|
||||||
|
margin-left: 12px;
|
||||||
|
color: #495057;
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 500;
|
||||||
|
letter-spacing: -0.2px;
|
||||||
|
}
|
||||||
|
|
||||||
.btn-submit {
|
.btn-submit {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
padding: 18px;
|
padding: 18px;
|
||||||
@ -382,6 +448,14 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="privacy-consent">
|
||||||
|
<label class="checkbox-container">
|
||||||
|
<input type="checkbox" id="privacyConsent" required>
|
||||||
|
<span class="checkmark"></span>
|
||||||
|
<span class="consent-text">개인정보 수집·이용 동의</span>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
|
||||||
<button type="submit" class="btn-submit" id="btnSubmit">
|
<button type="submit" class="btn-submit" id="btnSubmit">
|
||||||
포인트 적립하기
|
포인트 적립하기
|
||||||
</button>
|
</button>
|
||||||
@ -442,12 +516,18 @@
|
|||||||
|
|
||||||
const phone = document.getElementById('phone').value.trim();
|
const phone = document.getElementById('phone').value.trim();
|
||||||
const name = document.getElementById('name').value.trim();
|
const name = document.getElementById('name').value.trim();
|
||||||
|
const privacyConsent = document.getElementById('privacyConsent').checked;
|
||||||
|
|
||||||
if (!phone || !name) {
|
if (!phone || !name) {
|
||||||
showAlert('전화번호와 이름을 모두 입력해주세요.');
|
showAlert('전화번호와 이름을 모두 입력해주세요.');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!privacyConsent) {
|
||||||
|
showAlert('개인정보 수집·이용에 동의해주세요.');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
btnSubmit.disabled = true;
|
btnSubmit.disabled = true;
|
||||||
btnSubmit.textContent = '처리 중...';
|
btnSubmit.textContent = '처리 중...';
|
||||||
alertMsg.style.display = 'none';
|
alertMsg.style.display = 'none';
|
||||||
@ -462,7 +542,8 @@
|
|||||||
transaction_id: tokenInfo.transaction_id,
|
transaction_id: tokenInfo.transaction_id,
|
||||||
nonce: tokenInfo.nonce,
|
nonce: tokenInfo.nonce,
|
||||||
phone: phone,
|
phone: phone,
|
||||||
name: name
|
name: name,
|
||||||
|
privacy_consent: true
|
||||||
})
|
})
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@ -92,6 +92,7 @@ def create_qr_receipt_label(qr_url, transaction_id, total_amount, claimable_poin
|
|||||||
font_amount = ImageFont.truetype(font_path, 40) # 금액 (크게)
|
font_amount = ImageFont.truetype(font_path, 40) # 금액 (크게)
|
||||||
font_points = ImageFont.truetype(font_path, 36) # 포인트 (강조)
|
font_points = ImageFont.truetype(font_path, 36) # 포인트 (강조)
|
||||||
font_small = ImageFont.truetype(font_path, 28) # 안내 문구
|
font_small = ImageFont.truetype(font_path, 28) # 안내 문구
|
||||||
|
font_tiny = ImageFont.truetype(font_path, 18) # 개인정보 동의 (작게)
|
||||||
else:
|
else:
|
||||||
raise IOError("폰트 없음")
|
raise IOError("폰트 없음")
|
||||||
except (IOError, OSError):
|
except (IOError, OSError):
|
||||||
@ -101,6 +102,7 @@ def create_qr_receipt_label(qr_url, transaction_id, total_amount, claimable_poin
|
|||||||
font_amount = ImageFont.load_default()
|
font_amount = ImageFont.load_default()
|
||||||
font_points = ImageFont.load_default()
|
font_points = ImageFont.load_default()
|
||||||
font_small = ImageFont.load_default()
|
font_small = ImageFont.load_default()
|
||||||
|
font_tiny = ImageFont.load_default()
|
||||||
|
|
||||||
# 3. QR 코드 생성 (우측 상단) - 크기 및 해상도 개선
|
# 3. QR 코드 생성 (우측 상단) - 크기 및 해상도 개선
|
||||||
qr = qrcode.QRCode(
|
qr = qrcode.QRCode(
|
||||||
@ -162,6 +164,11 @@ def create_qr_receipt_label(qr_url, transaction_id, total_amount, claimable_poin
|
|||||||
# 안내 문구
|
# 안내 문구
|
||||||
guide_text = "QR 촬영하고 포인트 받으세요!"
|
guide_text = "QR 촬영하고 포인트 받으세요!"
|
||||||
draw.text((x_margin, y), guide_text, font=font_small, fill=0)
|
draw.text((x_margin, y), guide_text, font=font_small, fill=0)
|
||||||
|
y += 35
|
||||||
|
|
||||||
|
# 개인정보 동의 안내 (작은 글씨)
|
||||||
|
privacy_text = "(QR 스캔 시 개인정보 수집·이용에 동의한 것으로 간주됩니다)"
|
||||||
|
draw.text((x_margin, y), privacy_text, font=font_tiny, fill=0)
|
||||||
|
|
||||||
# 5. 테두리 (가위선 스타일)
|
# 5. 테두리 (가위선 스타일)
|
||||||
for i in range(2):
|
for i in range(2):
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user