giftcon_dev/app/Services/Dozn/DoznAccountAuthService.php

361 lines
12 KiB
PHP

<?php
namespace App\Services\Dozn;
use Carbon\Carbon;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Http;
use Illuminate\Support\Facades\Log;
final class DoznAccountAuthService
{
private string $mode;
private string $orgCode;
private string $apiKey;
private string $urlReal;
private string $urlTest;
private int $timeout;
public function __construct()
{
$cfg = config('services.dozn', []);
$this->mode = (string)($cfg['mode'] ?? 'real');
$this->orgCode = (string)($cfg['org_code'] ?? '');
$this->apiKey = (string)($cfg['api_key'] ?? '');
$this->urlReal = (string)($cfg['url_real'] ?? '');
$this->urlTest = (string)($cfg['url_test'] ?? '');
$this->timeout = (int)($cfg['timeout'] ?? 10);
}
/**
* 출금(out) 계좌 성명 인증 + 저장까지 한 번에 처리
*
* @param int $memNo 회원번호
* @param string $memberRealName 회원 실명(서버에서 꺼낸 값)
* @param string $inputDepositor 입력한 예금주(화면 입력)
* @param string $bankCode 은행코드(001~)
* @param string $account 계좌번호(숫자만 권장)
* @param bool $requireSameName 입력 예금주 == 회원실명 강제 여부
*
* @return array { ok, code, message, status, depositor, telegram_no }
*/
public function verifyAndSaveOutAccount(
int $memNo,
string $memberRealName,
string $inputDepositor,
string $bankCode,
string $account,
bool $requireSameName = true
): array {
$memNo = (int)$memNo;
$memberRealName = trim($memberRealName);
$inputDepositor = trim($inputDepositor);
$bankCode = trim($bankCode);
$account = preg_replace('/[^0-9]/', '', (string)$account);
// 은행코드 검증 (config/bank_code.php)
$bankMap = (array) config('bank_code.flat', []);
if ($bankCode === '' || !isset($bankMap[$bankCode])) {
return $this->fail('INVALID_BANK', '은행코드가 올바르지 않습니다.');
}
if ($account === '') {
return $this->fail('INVALID_ACCOUNT', '계좌번호를 입력해 주세요.');
}
if ($inputDepositor === '') {
return $this->fail('INVALID_DEPOSITOR', '예금주를 입력해 주세요.');
}
// 입력 예금주와 회원 실명 일치 강제 (네 CI3 로직과 동일)
if ($requireSameName && $memberRealName !== '' && $memberRealName !== $inputDepositor) {
// act_state=4(정보불일치) 기록(선택)
$this->upsertOutAccountState($memNo, $bankCode, $account, $inputDepositor, '4', [
'reason' => 'member_name_mismatch',
'member_real_name' => $memberRealName,
'input_depositor' => $inputDepositor,
]);
return $this->fail('NAME_MISMATCH', '가입자 성명과 예금주가 일치하지 않습니다!');
}
// telegram_no 발급 (일별 6자리)
$telegramNo = $this->issueTelegramNo();
// 요청 페이로드 (Dozn 스펙)
$payload = [
'org_code' => $this->orgCode,
'api_key' => $this->apiKey,
'telegram_no' => $telegramNo,
'bank_code' => $bankCode,
'account' => $account,
'check_depositor' => 'N', // 기존 코드 유지 (실명체크 identify_no는 추후)
// 'identify_no' => '761127',
];
// 요청 로그 insert
$logSeq = $this->logRequest($memNo, $payload, [
'input_depositor' => $inputDepositor,
'mode' => $this->mode,
]);
// 진행상태 기록 (2: 인증진행중)
$this->upsertOutAccountState($memNo, $bankCode, $account, $inputDepositor, '2', [
'log_seq' => $logSeq,
'telegram_no' => $telegramNo,
]);
// Dozn 호출
$resp = $this->callDozn($payload);
// 결과 로그 update
$this->logResult($logSeq, $resp['raw'] ?? []);
$status = (string)($resp['status'] ?? '');
$depositor = (string)($resp['depositor'] ?? '');
// 성공(200) 처리
if ($status === '200') {
if ($depositor === '' || $depositor !== $inputDepositor) {
// 정보 불일치 (4)
$this->upsertOutAccountState($memNo, $bankCode, $account, $inputDepositor, '4', [
'status' => $status,
'returned_depositor' => $depositor,
'input_depositor' => $inputDepositor,
'telegram_no' => $telegramNo,
]);
return [
'ok' => false,
'code' => 'DEPOSITOR_MISMATCH',
'message' => '예금주가 일치하지 않습니다!',
'status' => $status,
'depositor' => $depositor,
'telegram_no' => $telegramNo,
];
}
// 성공(3): 계좌 저장
$this->saveOutAccountSuccess($memNo, $bankCode, $account, $depositor, [
'status' => $status,
'telegram_no' => $telegramNo,
'result' => $resp['raw'] ?? [],
]);
return [
'ok' => true,
'code' => 'OK',
'message' => '인증완료 및 계좌번호가 등록되었습니다.',
'status' => $status,
'depositor' => $depositor,
'telegram_no' => $telegramNo,
];
}
// 잘못된 계좌(520)
if ($status === '520') {
$this->upsertOutAccountState($memNo, $bankCode, $account, $inputDepositor, '5', [
'status' => $status,
'telegram_no' => $telegramNo,
'result' => $resp['raw'] ?? [],
]);
return [
'ok' => false,
'code' => 'INVALID_ACCOUNT',
'message' => '잘 못된 계좌번호 입니다.',
'status' => $status,
'depositor' => $depositor,
'telegram_no' => $telegramNo,
];
}
// 기타 오류
$this->upsertOutAccountState($memNo, $bankCode, $account, $inputDepositor, '5', [
'status' => $status,
'telegram_no' => $telegramNo,
'result' => $resp['raw'] ?? [],
]);
return [
'ok' => false,
'code' => 'DOZN_ERROR',
'message' => ($status ?: 'ERR') . '|인증에 문제가 발생했습니다. 잠시후 다시 시도해주세요.',
'status' => $status,
'depositor' => $depositor,
'telegram_no' => $telegramNo,
];
}
// ------------------------------------------------------------------
// Dozn HTTP call
// ------------------------------------------------------------------
private function callDozn(array $payload): array
{
$url = $this->mode === 'test' ? $this->urlTest : $this->urlReal;
if ($url === '' || $this->orgCode === '' || $this->apiKey === '') {
return [
'status' => 'CONFIG',
'depositor' => '',
'raw' => ['error' => 'dozn_config_missing'],
];
}
try {
$res = Http::timeout($this->timeout)
->acceptJson()
->asJson()
->post($url, $payload);
$json = $res->json();
if (!is_array($json)) $json = [];
return [
'status' => (string)($json['status'] ?? ''),
'depositor' => (string)($json['depositor'] ?? ''),
'raw' => $json,
];
} catch (\Throwable $e) {
Log::warning('[dozn] request failed', [
'err' => $e->getMessage(),
]);
return [
'status' => 'HTTP',
'depositor' => '',
'raw' => ['error' => 'http_exception', 'message' => $e->getMessage()],
];
}
}
// ------------------------------------------------------------------
// telegram_no: mem_account_telegram_no 사용 (일별 6자리)
// ------------------------------------------------------------------
private function issueTelegramNo(): int
{
$today = Carbon::today()->toDateString();
return (int) DB::transaction(function () use ($today) {
// 오늘 발급된 마지막 telegram_no를 잠그고 다음 번호 생성
$last = DB::table('mem_account_telegram_no')
->where('date', $today)
->lockForUpdate()
->max('telegram_no');
$next = $last ? ((int)$last + 1) : 1;
if ($next > 999999) $next = 1;
DB::table('mem_account_telegram_no')->insert([
'telegram_no' => $next,
'date' => $today,
]);
return $next;
});
}
// ------------------------------------------------------------------
// Logs: mem_account_log
// ------------------------------------------------------------------
private function logRequest(int $memNo, array $payload, array $meta = []): int
{
$now = Carbon::now()->format('Y-m-d H:i:s');
$req = array_merge($payload, $meta);
$reqJson = json_encode($req, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
if ($reqJson === false) $reqJson = '{}';
return (int) DB::table('mem_account_log')->insertGetId([
'mem_no' => $memNo,
'request_data' => $reqJson,
'request_time' => $now,
'result_data' => '{}',
'result_time' => $now,
]);
}
private function logResult(int $seq, array $result): void
{
if ($seq <= 0) return;
$now = Carbon::now()->format('Y-m-d H:i:s');
$resJson = json_encode($result, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
if ($resJson === false) $resJson = '{}';
DB::table('mem_account_log')
->where('seq', $seq)
->update([
'result_data' => $resJson,
'result_time' => $now,
]);
}
// ------------------------------------------------------------------
// mem_account: 회원당 out 1개 유지 (스키마 유지, 코드로 강제)
// ------------------------------------------------------------------
private function upsertOutAccountState(
int $memNo,
string $bankCode,
string $account,
string $depositor,
string $actState,
array $confirmLog = []
): void {
$bankMap = (array) config('bank_code.flat', []);
$bankName = (string)($bankMap[$bankCode] ?? '');
$now = Carbon::now()->format('Y-m-d H:i:s');
DB::transaction(function () use ($memNo, $bankCode, $bankName, $account, $depositor, $actState, $confirmLog, $now) {
// mem_no + act_type='out' 기준으로 row 잠금
$row = DB::table('mem_account')
->where('mem_no', $memNo)
->where('act_type', 'out')
->lockForUpdate()
->first();
$data = [
'mem_no' => $memNo,
'act_type' => 'out',
'act_state' => $actState,
'bank_code' => $bankCode,
'bank_name' => $bankName,
'bank_act_name' => $depositor,
'bank_act_num' => $account,
'in_date' => $now,
'act_date' => $now,
'confirm_log' => json_encode($confirmLog ?: new \stdClass(), JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES),
];
if ($row) {
DB::table('mem_account')->where('seq', $row->seq)->update($data);
} else {
DB::table('mem_account')->insert($data);
}
});
}
private function saveOutAccountSuccess(
int $memNo,
string $bankCode,
string $account,
string $depositor,
array $confirmLog = []
): void {
// 성공은 act_state=3
$this->upsertOutAccountState($memNo, $bankCode, $account, $depositor, '3', $confirmLog);
}
private function fail(string $code, string $message): array
{
return [
'ok' => false,
'code' => $code,
'message' => $message,
];
}
}