236 lines
8.2 KiB
PHP
236 lines
8.2 KiB
PHP
<?php
|
|
|
|
namespace App\Http\Controllers\Web\Payment;
|
|
|
|
use App\Http\Controllers\Controller;
|
|
use App\Services\Payments\PaymentService;
|
|
use Illuminate\Http\Request;
|
|
|
|
final class DanalController extends Controller
|
|
{
|
|
public function __construct(
|
|
private readonly PaymentService $service,
|
|
) {}
|
|
|
|
// 결제 시작(POST 권장)
|
|
public function start(Request $request)
|
|
{
|
|
$data = $request->validate([
|
|
'oid' => ['required','string','max:64'],
|
|
'method' => ['required','in:card,vact,phone,wire'],
|
|
'card_kind' => ['nullable','in:general,exchange'],
|
|
'phone_mode' => ['nullable','in:prod,dev'],
|
|
'is_mobile' => ['nullable','boolean'],
|
|
]);
|
|
|
|
$memNo = $this->currentMemNo($request);
|
|
if ($memNo <= 0) abort(403);
|
|
|
|
$out = $this->service->start(
|
|
$data['oid'],
|
|
$memNo,
|
|
$data['method'],
|
|
[
|
|
'card_kind' => $data['card_kind'] ?? null,
|
|
'phone_mode' => $data['phone_mode'] ?? null,
|
|
'is_mobile' => (bool)($data['is_mobile'] ?? false),
|
|
]
|
|
);
|
|
|
|
if (($out['type'] ?? '') === 'redirect') {
|
|
return view('web.payments.danal.redirect', [
|
|
'actionUrl' => $out['start']['actionUrl'],
|
|
'params' => $out['start']['params'],
|
|
'acceptCharset' => $out['start']['acceptCharset'] ?? 'EUC-KR',
|
|
]);
|
|
}
|
|
|
|
return view('web.payments.danal.result', $out);
|
|
}
|
|
|
|
// 카드 RETURNURL
|
|
public function cardReturn(Request $request)
|
|
{
|
|
$token = (string)$request->query('a', '');
|
|
if ($token === '') abort(404);
|
|
|
|
if (($out['ok'] ?? false) && ($out['status'] ?? '') === 'paid') {
|
|
$attemptId = (int)($out['meta']['attempt_id'] ?? 0);
|
|
return view('web.payments.danal.finish_top_action', [
|
|
'action' => 'close_modal',
|
|
'title' => '결제완료',
|
|
'message' => '결제가 완료되었습니다. 구매페이지로 이동합니다.',
|
|
'redirect' => url("/mypage/usage?attempt_id={$attemptId}"),
|
|
]);
|
|
}
|
|
|
|
return view('web.payments.danal.finish_top_action', [
|
|
'action' => 'close_modal',
|
|
'title' => '결제실패',
|
|
'message' => '결제에 실패했습니다.',
|
|
]);
|
|
}
|
|
|
|
// 가상계좌 RETURNURL
|
|
public function vactReturn(Request $request)
|
|
{
|
|
$token = (string)$request->query('a', '');
|
|
if ($token === '') abort(404);
|
|
|
|
$out = $this->service->handleVactReturn($token, $request->all());
|
|
|
|
if (($out['ok'] ?? false) && ($out['status'] ?? '') === 'issued') {
|
|
$attemptId = (int)($out['meta']['attempt_id'] ?? 0);
|
|
return view('web.payments.danal.finish_top_action', [
|
|
'action' => 'close_modal',
|
|
'title' => '가상계좌 발급',
|
|
'message' => '가상계좌가 발급되었습니다. 입금 후 결제가 완료됩니다. 구매페이지로 이동합니다.',
|
|
'redirect' => url("/mypage/usage?attempt_id={$attemptId}"),
|
|
]);
|
|
}
|
|
|
|
return view('web.payments.danal.finish_top_action', [
|
|
'action' => 'close_modal',
|
|
'title' => '처리실패',
|
|
'message' => '가상계좌 처리에 실패했습니다.',
|
|
]);
|
|
}
|
|
|
|
|
|
// 가상계좌 NOTIURL (반드시 OK)
|
|
public function vactNoti(Request $request)
|
|
{
|
|
$this->service->handleVactNoti($request->all());
|
|
return response('OK', 200)->header('Content-Type', 'text/plain');
|
|
}
|
|
|
|
public function wireReturn(Request $request)
|
|
{
|
|
$token = (string)$request->query('a', '');
|
|
if ($token === '') abort(404);
|
|
|
|
// 🔥 예외 렌더러가 죽지 않도록 요청값 UTF-8 정리
|
|
$post = $this->forceUtf8Array($request->all());
|
|
|
|
$out = $this->service->handleWireReturn($token, $post);
|
|
|
|
if (($out['ok'] ?? false) && ($out['status'] ?? '') === 'paid') {
|
|
$attemptId = (int)($out['meta']['attempt_id'] ?? 0);
|
|
return view('web.payments.danal.finish_top_action', [
|
|
'action' => 'close_modal',
|
|
'title' => '결제완료',
|
|
'message' => '결제가 완료되었습니다. 구매페이지로 이동합니다.',
|
|
'redirect' => url("/mypage/usage?attempt_id={$attemptId}"),
|
|
]);
|
|
}
|
|
|
|
return view('web.payments.danal.finish_top_action', [
|
|
'action' => 'close_modal',
|
|
'title' => '결제실패',
|
|
'message' => '결제에 실패했습니다.',
|
|
]);
|
|
}
|
|
|
|
public function wireNoti(Request $request)
|
|
{
|
|
// 🔥 예외 렌더러가 죽지 않도록 요청값 UTF-8 정리
|
|
$post = $this->forceUtf8Array($request->all());
|
|
|
|
// NOTI는 성공/실패와 무관하게 OK만 주면 다날 재시도 종료
|
|
$this->service->handleWireNoti($post);
|
|
return response('OK', 200)->header('Content-Type', 'text/plain');
|
|
}
|
|
|
|
private function forceUtf8Array(array $arr): array
|
|
{
|
|
foreach ($arr as $k => $v) {
|
|
if (is_array($v)) {
|
|
$arr[$k] = $this->forceUtf8Array($v);
|
|
continue;
|
|
}
|
|
if (!is_string($v) || $v === '') continue;
|
|
|
|
// 이미 UTF-8이면 그대로
|
|
if (function_exists('mb_check_encoding') && mb_check_encoding($v, 'UTF-8')) continue;
|
|
|
|
// 다날은 EUC-KR 가능성이 높음 → UTF-8로 변환(깨진 바이트 제거)
|
|
$out = @iconv('EUC-KR', 'UTF-8//IGNORE', $v);
|
|
if ($out === false) $out = '';
|
|
if (function_exists('mb_check_encoding') && !mb_check_encoding($out, 'UTF-8')) {
|
|
$out = @iconv('UTF-8', 'UTF-8//IGNORE', $out) ?: '';
|
|
}
|
|
$arr[$k] = $out;
|
|
}
|
|
return $arr;
|
|
}
|
|
|
|
// 휴대폰 TargetURL
|
|
public function phoneReturn(Request $request)
|
|
{
|
|
$out = $this->service->handlePhoneReturn($request->all());
|
|
|
|
if (($out['ok'] ?? false) && ($out['status'] ?? '') === 'paid') {
|
|
$attemptId = (int)($out['meta']['attempt_id'] ?? 0);
|
|
return view('web.payments.danal.finish_top_action', [
|
|
'action' => 'close_modal',
|
|
'title' => '결제완료',
|
|
'message' => '결제가 완료되었습니다. 구매페이지로 이동합니다.',
|
|
'redirect' => url("/mypage/usage?attempt_id={$attemptId}"),
|
|
]);
|
|
}
|
|
|
|
return view('web.payments.danal.finish_top_action', [
|
|
'action' => 'close_modal',
|
|
'title' => '결제실패',
|
|
'message' => '결제에 실패했습니다.',
|
|
]);
|
|
}
|
|
|
|
// 휴대폰 BackURL(취소)
|
|
public function phoneCancel(Request $request)
|
|
{
|
|
$out = $this->service->handlePhoneCancel($request->all());
|
|
|
|
return view('web.payments.danal.finish_top_action', [
|
|
'action' => 'close_modal',
|
|
'message' => '결제가 취소되었습니다.',
|
|
'title' => '결제취소',
|
|
]);
|
|
}
|
|
|
|
// 카드/가상계좌 CancelURL
|
|
public function cancel(Request $request)
|
|
{
|
|
$token = (string)$request->query('a', '');
|
|
if ($token === '') abort(404);
|
|
|
|
$out = $this->service->handleCancel($token);
|
|
|
|
// 취소면: iframe 닫고 showMsg 실행
|
|
if (($out['meta']['code'] ?? '') === 'CANCEL') {
|
|
return view('web.payments.danal.finish_top_action', [
|
|
'action' => 'close_modal',
|
|
'message' => '결제가 취소되었습니다.',
|
|
'title' => '결제취소',
|
|
]);
|
|
}
|
|
|
|
return view('web.payments.danal.result', $out);
|
|
}
|
|
|
|
private function currentMemNo(Request $request): int
|
|
{
|
|
// 프로젝트에 맞게 연결해라:
|
|
// 1) Auth::user()->mem_no 가 있으면 그걸 쓰고,
|
|
// 2) local 테스트용은 request('mem_no') 허용
|
|
$u = $request->user();
|
|
if ($u && isset($u->mem_no)) return (int)$u->mem_no;
|
|
|
|
if (app()->environment('local')) {
|
|
return (int)$request->input('mem_no', 0);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
}
|