giftcon_dev/app/Http/Controllers/Web/Auth/RegisterController.php
2026-01-26 12:59:59 +09:00

299 lines
11 KiB
PHP

<?php
namespace App\Http\Controllers\Web\Auth;
use App\Http\Controllers\Controller;
use App\Repositories\Member\MemberAuthRepository;
use App\Services\Danal\DanalAuthtelService;
use App\Rules\RecaptchaV3Rule;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Validator;
use Illuminate\Support\Facades\Log;
class RegisterController extends Controller
{
public function showStep0()
{
return view('web.auth.register');
}
public function showTerms(Request $request)
{
// Step0 스킵 방지 (최소)
if (($request->session()->get('signup.step') ?? 0) < 1) {
return redirect()->route('web.auth.register');
}
return view('web.auth.register_terms');
}
public function postPhoneCheck(Request $request, MemberAuthRepository $repo)
{
$v = Validator::make($request->all(), [
'phone' => ['required', 'string', 'max:20'],
'g-recaptcha-response' => ['required', new RecaptchaV3Rule('register_phone_check')],
], [
'phone.required' => '휴대폰 번호를 입력해 주세요.',
'g-recaptcha-response.required' => '보안 검증에 실패했습니다. 다시 시도해 주세요.',
]);
if ($v->fails()) {
return response()->json(['ok' => false, 'message' => $v->errors()->first()], 422);
}
$ip4 = $request->ip() ?: '';
$result = $repo->step0PhoneCheck((string)$request->input('phone'), $ip4);
if (!($result['ok'] ?? false)) {
$reason = $result['reason'] ?? 'error';
// blocked류만 403, 그 외는 422(원하면 already_member는 200으로 바꿔도 됨)
$status = in_array($reason, ['blocked', 'blocked_ip'], true) ? 403 : 422;
return response()->json([
'ok' => false,
'reason' => $reason,
'message' => $result['message'] ?? '처리에 실패했습니다.',
'redirect' => $result['redirect'] ?? null,
], $status);
}
$request->session()->put('signup.step', 1);
$request->session()->put('signup.phone', $result['phone']); // 필요하면
$request->session()->save();
return response()->json([
'ok' => true,
'reason' => $result['reason'] ?? 'ok',
'message' => $result['message'] ?? '',
'redirect' => $result['redirect'] ?? null,
'phone' => $result['phone'] ?? null,
], 200);
}
public function termsSubmit(Request $request)
{
$v = Validator::make($request->all(), [
'agree_terms' => ['required', 'in:1'],
'agree_privacy' => ['required', 'in:1'],
'agree_age' => ['required', 'in:1'],
'agree_marketing' => ['nullable', 'in:1'],
], [
'agree_terms.required' => '이용약관에 동의해 주세요.',
'agree_privacy.required' => '개인정보 수집·이용에 동의해 주세요.',
'agree_age.required' => '만 14세 이상만 가입할 수 있습니다.',
]);
if ($v->fails()) {
// AJAX면 JSON으로
if ($request->expectsJson()) {
return response()->json([
'ok' => false,
'message' => $v->errors()->first(),
'errors' => $v->errors(),
], 422);
}
// 일반 submit이면 기존처럼
return back()->withErrors($v)->withInput();
}
// 약관 동의값 저장(세션)
$request->session()->put('register.terms', [
'agree_terms' => true,
'agree_privacy' => true,
'agree_age' => true,
'agree_marketing' => (bool)$request->input('agree_marketing'),
'agreed_at' => now()->toDateTimeString(),
'ip' => $request->ip(),
'ua' => substr((string)$request->userAgent(), 0, 500),
]);
$request->session()->save();
// AJAX면: 다날 준비값 생성 후 popup 정보 반환
if ($request->expectsJson()) {
$danal = app(\App\Services\Danal\DanalAuthtelService::class)->prepare([
'targetUrl' => route('web.auth.register.danal.result'),
'backUrl' => route('web.auth.register.terms'),
'cpTitle' => request()->getHost(),
]);
if (!($danal['ok'] ?? false)) {
return response()->json([
'ok' => false,
'message' => $danal['message'] ?? '본인인증 준비에 실패했습니다. 잠시 후 다시 시도해 주세요.',
], 500);
}
// 필요하면 트랜잭션 정보 세션 저장
$request->session()->put('register.danal', [
'txid' => $danal['txid'] ?? null,
'created_at' => now()->toDateTimeString(),
]);
$request->session()->save();
return response()->json([
'ok' => true,
'reason' => 'danal_ready',
'popup' => [
'url' => route('web.auth.register.danal.start'),
'fields' => $danal['fields'], // Start.php로 보낼 hidden inputs
],
]);
}
return redirect()->route('web.auth.register.terms')
->with('ui_dialog', [
'type' => 'alert',
'title' => '완료',
'message' => "약관 동의가 저장되었습니다.\n\n다음 단계(본인인증)를 진행합니다.",
]);
}
public function danalStart(Request $request)
{
// fields는 JSON으로 받을 것
$fieldsJson = (string)$request->input('fields', '');
$fields = json_decode($fieldsJson, true);
if (!is_array($fields) || empty($fields)) {
abort(400, 'Invalid Danal fields');
}
return view('web.auth.danal_autosubmit', [
'action' => 'https://wauth.teledit.com/Danal/WebAuth/Web/Start.php',
'fields' => $fields,
]);
}
public function danalResult(Request $request, DanalAuthtelService $danal)
{
$payload = $request->all();
if (config('app.debug')) {
Log::info('[DANAL][RESULT] payload keys', [
'method' => $request->method(),
'url' => $request->fullUrl(),
'keys' => array_keys($payload),
]);
Log::info('[DANAL][RESULT] payload', $this->maskDanalPayloadForLog($payload));
}
$tid = (string)($payload['TID'] ?? '');
if ($tid === '') {
return response()->view('web.auth.danal_finish_top', [
'ok' => false,
'message' => 'TID가 없습니다.',
'redirect' => route('web.auth.register.terms'),
]);
}
// CI와 동일: TID로 CONFIRM 호출해서 RETURNCODE를 받는다 (dndata 사용 안함)
$res = $danal->confirm($tid, 0, 1);
if (config('app.debug')) {
Log::info('[DANAL][CONFIRM] keys', ['keys' => array_keys($res)]);
Log::info('[DANAL][CONFIRM] res', $this->maskDanalPayloadForLog($res));
}
$ok = (($res['RETURNCODE'] ?? '') === '0000');
if (!$ok) {
return response()->view('web.auth.danal_finish_top', [
'ok' => false,
'message' => ($res['RETURNMSG'] ?? '본인인증에 실패했습니다.') . ' (' . ($res['RETURNCODE'] ?? 'NO_CODE') . ')',
'redirect' => route('web.auth.register.terms'),
]);
}
/* 다날 통신 성공 후 체크*/
$normPhone = function (?string $p) {
$p = preg_replace('/\D+/', '', (string)$p);
return $p;
};
$signupPhone = $normPhone(data_get($request->session()->get('signup', []), 'phone')); //처음 입력 전화번호
$passPhone = $normPhone($res['PHONE'] ?? null); //인증받은 전화번호
// signup.phone vs PASS PHONE 비교
if ($signupPhone === '' || $passPhone === '' || $signupPhone !== $passPhone) {
$request->session()->flush();
$request->session()->save();
return response()->view('web.auth.danal_finish_top', [
'ok' => false,
'message' => '인증에 사용한 휴대폰 번호가 가입 진행 번호와 일치하지 않습니다. 다시 진행해 주세요.',
'redirect' => route('web.auth.register'),
]);
}
// 통신사 제한
$carrier = (string)($res['CARRIER'] ?? '');
$blockedCarriers = ['SKT_MVNO', 'KT_MVNO', 'LGT_MVNO']; // 알뜰폰
if (in_array($carrier, $blockedCarriers, true)) {
$request->session()->flush();
$request->session()->save();
return response()->view('web.auth.danal_finish_top', [
'ok' => false,
'message' => '죄송합니다. 알뜰폰(또는 일부 통신사) 휴대전화는 가입할 수 없습니다.',
'redirect' => route('web.auth.register'),
]);
}
// 성공: 다음 단계에서 쓸 데이터 세션 저장
$request->session()->put('register.pass_verified', true);
$request->session()->put('register.pass_payload', $res);
$request->session()->save();
return response()->view('web.auth.danal_finish_top', [
'ok' => true,
'message' => '본인인증이 완료되었습니다.',
'redirect' => route('web.auth.register.profile'),
]);
}
public function showProfileForm(Request $request)
{
if (!$request->session()->get('register.pass_verified')) {
return redirect()->route('web.auth.register.terms')
->with('ui_dialog', [
'type'=>'alert',
'title'=>'안내',
'message'=>'본인인증 후 진행해 주세요.',
]);
}
return view('web.auth.profile');
}
private function maskDanalPayloadForLog(array $payload): array
{
$masked = $payload;
$sensitiveKeys = [
'CI','DI','NAME','BIRTH','SEX','GENDER','TEL','PHONE','MOBILE',
'TID','TXID','TOKEN','ENC','CERT','SSN'
];
foreach ($masked as $k => $v) {
$keyUpper = strtoupper((string)$k);
foreach ($sensitiveKeys as $sk) {
if (str_contains($keyUpper, $sk)) {
$masked[$k] = '***';
break;
}
}
}
return $masked;
}
}