giftcon_dev/app/Services/MypageInfoService.php

133 lines
4.9 KiB
PHP

<?php
declare(strict_types=1);
namespace App\Services;
use App\Repositories\Mypage\MypageInfoRepository;
use App\Support\LegacyCrypto\CiSeedCrypto;
use Illuminate\Support\Arr;
use Illuminate\Support\Facades\DB;
final class MypageInfoService
{
public function __construct(
private readonly MypageInfoRepository $repo,
private readonly CiSeedCrypto $seed,
) {}
/**
* 마이페이지 연락처 변경(PASS) 검증
*
* 입력:
* - $sess: session('_sess') 배열
* - $passPayload: session('mypage.pass.payload') 또는 session('pass.payload') 배열
*
* 반환:
* - ['ok'=>bool, 'message'=>string, 'enc_phone'=>string, 'raw_phone'=>string, 'ci'=>string, 'carrier'=>string]
*/
public function validatePassPhoneChange(array $sess, array $passPayload): array
{
$memNo = (int) ($sess['_mno'] ?? 0);
if ($memNo <= 0) {
return $this->fail('로그인 정보가 확인되지 않습니다. 다시 로그인 후 시도해 주세요.');
}
$rawPhone = $this->normalizePhone((string) Arr::get($passPayload, 'PHONE', ''));
$ci = (string) Arr::get($passPayload, 'CI', '');
$carrier = strtoupper((string) Arr::get($passPayload, 'CARRIER', ''));
if ($rawPhone === '' || $ci === '') {
return $this->fail('인증 정보가 올바르지 않습니다. 다시 시도해 주세요.');
}
// 통신사 제한: SKT/KTF/LGT만 허용, 나머지 전부 차단 + MVNO 차단
$allowCarriers = ['SKT', 'KTF', 'LGT'];
if (!in_array($carrier, $allowCarriers, true) || $carrier === 'MVNO') {
return $this->fail('죄송합니다. SKT/KT/LG U+ 휴대전화만 인증할 수 있습니다. (알뜰폰/기타 통신사 불가)');
}
// PASS 전화번호 암호화 (CI 레거시 방식)
$encPassPhone = (string) $this->seed->encrypt($rawPhone);
// 기존 회원 전화번호와 동일하면 변경 불가
$encMemberPhone = (string) ($sess['_mcell'] ?? '');
if ($encMemberPhone !== '' && $encPassPhone !== '' && hash_equals($encMemberPhone, $encPassPhone)) {
return $this->fail('현재 등록된 연락처와 동일합니다. 다른 번호로 인증해 주세요.');
}
// PASS 전화번호가 DB에 존재하면 변경 불가
// "존재한다면 이전에 가입된 전화번호가 있습니다. 관리자 문의"
if ($this->repo->existsEncryptedCell($encPassPhone, $memNo)) {
return $this->fail('이미 가입된 휴대폰 번호가 있습니다. 관리자에게 문의해 주세요.');
}
// CI가 회원정보 mem_info.ci와 일치해야 통과
$memberCi = $this->repo->getMemberCi($memNo);
if ($memberCi === '' || !hash_equals($memberCi, $ci)) {
return $this->fail('가입된 회원정보와 일치하지 않습니다. 관리자에게 문의해 주세요.');
}
return [
'ok' => true,
'message' => '검증이 완료되었습니다.',
'enc_phone' => $encPassPhone,
'raw_phone' => $rawPhone,
'ci' => $ci,
'carrier' => $carrier,
];
}
/**
* 최종 저장: mem_info.cell 업데이트
* @return array ['ok'=>bool, 'message'=>string, '_cell'=>string]
*/
public function commitPhoneChange(int $memNo, array $passPayload): array
{
$Phone = $this->normalizePhone((string) Arr::get($passPayload, 'PHONE', ''));
$encPhone = (string) $this->seed->encrypt($Phone);
if ($memNo <= 0 || $encPhone === '') {
return $this->fail('저장할 정보가 올바르지 않습니다.');
}
return DB::transaction(function () use ($memNo, $encPhone) {
// 한번 더 중복 방어(레이스 컨디션)
if ($this->repo->existsEncryptedCell($encPhone, $memNo)) {
return $this->fail('이미 가입된 휴대폰 번호가 있습니다. 관리자에게 문의해 주세요.');
}
$affected = $this->repo->updateEncryptedCell($memNo, $encPhone);
if ($affected < 1) {
return $this->fail('연락처 저장에 실패했습니다. 잠시 후 다시 시도해 주세요.');
}
return [
'ok'=>true,
'message'=>'연락처가 변경되었습니다.',
'_cell' => $encPhone,
];
});
}
/**
* 휴대폰 정규화 (숫자만)
*/
private function normalizePhone(string $v): string
{
return preg_replace('/\D+/', '', $v) ?: '';
}
private function fail(string $message): array
{
return [
'ok' => false,
'message' => $message,
'enc_phone' => '',
'raw_phone' => '',
'ci' => '',
'carrier' => '',
];
}
}