223 lines
7.1 KiB
PHP
223 lines
7.1 KiB
PHP
<?php
|
|
|
|
namespace App\Services\Danal;
|
|
|
|
use Illuminate\Support\Arr;
|
|
use Illuminate\Support\Str;
|
|
|
|
/**
|
|
* Danal PASS(Authtel) 준비 서비스
|
|
*
|
|
* - prepare(): 다날 Start 페이지로 POST할 hidden fields를 구성
|
|
* - callTrans / makeFormFields / getRandom 등은 별도 Client로 분리하는 게 이상적이지만,
|
|
* 지금은 "작동 우선"으로 이 파일 하나로 정리할 수 있게 구성합니다.
|
|
*/
|
|
final class DanalAuthtelService
|
|
{
|
|
private string $serviceUrl;
|
|
private string $cpid;
|
|
private string $cppwd;
|
|
private string $charset;
|
|
private int $connectTimeout;
|
|
private int $timeout;
|
|
private bool $debug;
|
|
|
|
public function __construct()
|
|
{
|
|
$cfg = (array) config('danal.authtel', []);
|
|
|
|
$this->serviceUrl = (string) Arr::get($cfg, 'service_url', '');
|
|
$this->cpid = (string) Arr::get($cfg, 'cpid', '');
|
|
$this->cppwd = (string) Arr::get($cfg, 'cppwd', '');
|
|
$this->charset = (string) Arr::get($cfg, 'charset', 'UTF-8');
|
|
$this->connectTimeout = (int) Arr::get($cfg, 'connect_timeout', 5);
|
|
$this->timeout = (int) Arr::get($cfg, 'timeout', 30);
|
|
$this->debug = (bool) Arr::get($cfg, 'debug', false);
|
|
}
|
|
|
|
public function prepare(array $opt): array
|
|
{
|
|
// 필수 설정 체크
|
|
if ($this->cpid === '' || $this->cppwd === '' || $this->serviceUrl === '') {
|
|
return ['ok' => false, 'message' => 'DANAL 설정(CPID/CPPWD/SERVICE_URL)이 없습니다.'];
|
|
}
|
|
|
|
$targetUrl = (string) ($opt['targetUrl'] ?? '');
|
|
$backUrl = (string) ($opt['backUrl'] ?? '');
|
|
|
|
if ($targetUrl === '') return ['ok' => false, 'message' => 'targetUrl 누락'];
|
|
if ($backUrl === '') return ['ok' => false, 'message' => 'backUrl 누락'];
|
|
|
|
// 기본 타이틀
|
|
$cpTitle = (string) ($opt['cpTitle'] ?? request()->getHost());
|
|
|
|
// TransR
|
|
$trans = [
|
|
'TXTYPE' => 'ITEMSEND',
|
|
'SERVICE' => 'UAS',
|
|
'AUTHTYPE' => '36',
|
|
'CPID' => $this->cpid,
|
|
'CPPWD' => $this->cppwd,
|
|
'TARGETURL' => $targetUrl,
|
|
'CPTITLE' => $cpTitle,
|
|
];
|
|
|
|
// 다날 서버 통신
|
|
$res = $this->callTrans($trans);
|
|
|
|
if (($res['RETURNCODE'] ?? '') !== '0000') {
|
|
return [
|
|
'ok' => false,
|
|
'message' => $this->formatReturnMsg($res),
|
|
];
|
|
}
|
|
|
|
// CI3의 ByPassValue
|
|
$byPass = [
|
|
// CI의 GetBgColor(0~10 => "00"~"10") 느낌 유지
|
|
'BgColor' => $this->getBgColor($this->getRandom(0, 10)),
|
|
'BackURL' => $backUrl,
|
|
'IsCharSet' => $this->charset,
|
|
'ByBuffer' => 'This value bypass to CPCGI Page',
|
|
'ByAnyName' => 'AnyValue',
|
|
];
|
|
|
|
// Start.php로 보낼 hidden fields 구성
|
|
// - 여기서는 절대 e()/htmlspecialchars 같은 escape를 하지 마세요.
|
|
// - 프론트에서 input.value로 넣으면 브라우저가 처리합니다.
|
|
$fields = array_merge(
|
|
$this->makeFormFields($res, ['RETURNCODE', 'RETURNMSG']),
|
|
$this->makeFormFields($byPass)
|
|
);
|
|
|
|
return [
|
|
'ok' => true,
|
|
'txid' => $res['TID'] ?? $res['TXID'] ?? null,
|
|
'fields' => $fields,
|
|
];
|
|
}
|
|
|
|
/**
|
|
* 다날 서버 통신
|
|
* @return array<string,string>
|
|
*/
|
|
public function callTrans(array $reqData): array
|
|
{
|
|
// x-www-form-urlencoded
|
|
$body = http_build_query($reqData, '', '&', PHP_QUERY_RFC3986);
|
|
|
|
$ch = curl_init();
|
|
curl_setopt($ch, CURLOPT_POST, 1);
|
|
|
|
// SSL 검증은 끄지 마세요 (운영 보안)
|
|
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, 1);
|
|
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 2);
|
|
|
|
curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, $this->connectTimeout);
|
|
curl_setopt($ch, CURLOPT_TIMEOUT, $this->timeout);
|
|
curl_setopt($ch, CURLOPT_URL, $this->serviceUrl);
|
|
curl_setopt($ch, CURLOPT_HTTPHEADER, [
|
|
'Content-Type: application/x-www-form-urlencoded; charset=' . $this->charset,
|
|
]);
|
|
curl_setopt($ch, CURLOPT_POSTFIELDS, $body);
|
|
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
|
|
|
|
$resStr = curl_exec($ch);
|
|
|
|
if (($errno = curl_errno($ch)) !== 0) {
|
|
$err = curl_error($ch) ?: 'curl error';
|
|
curl_close($ch);
|
|
|
|
return [
|
|
'RETURNCODE' => '-1',
|
|
'RETURNMSG' => 'NETWORK ERROR(' . $errno . ':' . $err . ')',
|
|
];
|
|
}
|
|
|
|
curl_close($ch);
|
|
|
|
// 다날 응답 파싱
|
|
return $this->str2data((string) $resStr);
|
|
}
|
|
|
|
/** @return array<string,string> */
|
|
private function str2data(string $str): array
|
|
{
|
|
$data = [];
|
|
foreach (explode('&', $str) as $line) {
|
|
$kv = explode('=', $line, 2);
|
|
if (count($kv) === 2) {
|
|
$data[$kv[0]] = $kv[1];
|
|
}
|
|
}
|
|
return $data;
|
|
}
|
|
|
|
/**
|
|
* hidden fields 만들기 (escape 하지 않음)
|
|
* @param array<string, mixed> $arr
|
|
* @param array<int, string> $excludeKeys
|
|
* @return array<string, string>
|
|
*/
|
|
private function makeFormFields(array $arr, array $excludeKeys = [], string $prefix = ''): array
|
|
{
|
|
$out = [];
|
|
$preLen = strlen(trim($prefix));
|
|
|
|
foreach ($arr as $key => $value) {
|
|
$key = (string) $key;
|
|
if ($key === '' || trim($key) === '') continue;
|
|
|
|
if (in_array($key, $excludeKeys, true)) continue;
|
|
|
|
if ($preLen > 0 && substr($key, 0, $preLen) !== $prefix) continue;
|
|
|
|
// 다날은 value에 urlencoded가 들어갈 수 있음
|
|
// 그대로 보내는 게 원칙이지만, 필요 시 여기서 정책적으로 urldecode/encode 조절 가능
|
|
$out[$key] = is_scalar($value) || $value === null ? (string) $value : json_encode($value, JSON_UNESCAPED_UNICODE);
|
|
}
|
|
|
|
return $out;
|
|
}
|
|
|
|
private function getBgColor($bgColor): string
|
|
{
|
|
$color = 0;
|
|
$i = (int) $bgColor;
|
|
if ($i > 0 && $i < 11) $color = $i;
|
|
return sprintf('%02d', $color);
|
|
}
|
|
|
|
private function getRandom(int $min, int $max): int
|
|
{
|
|
return random_int($min, $max);
|
|
}
|
|
|
|
/** @param array<string,string> $res */
|
|
private function formatReturnMsg(array $res): string
|
|
{
|
|
$msg = (string)($res['RETURNMSG'] ?? 'DANAL ERROR');
|
|
$code = (string)($res['RETURNCODE'] ?? 'NO_CODE');
|
|
return $msg . ' (' . $code . ')';
|
|
}
|
|
|
|
public function confirm(string $tid, int $confirmOption = 0, int $idenOption = 1): array
|
|
{
|
|
$req = [
|
|
'TXTYPE' => 'CONFIRM',
|
|
'TID' => $tid,
|
|
'CONFIRMOPTION' => $confirmOption,
|
|
'IDENOPTION' => $idenOption,
|
|
];
|
|
|
|
// CI 주석: CONFIRMOPTION=1이면 CPID/ORDERID 필수
|
|
if ($confirmOption === 1) {
|
|
$req['CPID'] = $this->cpid;
|
|
}
|
|
|
|
return $this->callTrans($req);
|
|
}
|
|
|
|
|
|
}
|