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 */ 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 */ 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 $arr * @param array $excludeKeys * @return array */ 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 $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); } }