04강: HTTP 클라이언트 PG사 연동 & Context 트레이스 헬퍼
실습 프로젝트: 지니샵(JinyShop) 온라인 도서 쇼핑몰
주제: 라라벨 HTTP 클라이언트(Http::retry()), PG사 REST 통신,Http::fake()가상 모킹, Context 요청 트레이스 추적 및 전역 헬퍼 함수
등장인물: 🐱 지니(시니어 멘토), 👧 도로시(주니어 개발자), 🐶 토토(개발 보조견)
공식 문서 연계: HTTP 클라이언트 (HTTP Client), 컨텍스트 (Context), 헬퍼 (Helpers)
💬 1. 지니와 도로시의 티키타카 회의
👧 도로시: “지니! 실제 토스페이먼츠나 카카오페이 서버로 결제 승인을 요청할 때 네트워크가 잠깐 끊기거나 PG사 서버가 1초 동안 응답이 없으면 우리 서점 결제가 다 튕겨 나가잖아요! 외부 통신을 안전하게 재시도(Retry)하고 타임아웃을 거는 방법은 없나요? 그리고 복잡한 주문 흐름 속에서 이 결제가 어떤 독자의 어떤 장바구니에서 시작되었는지 로그를 추적하려면 어떻게 해야 해요?”
🐱 지니: “도로시, 바로 그럴 때 사용하는 최고의 도구가 바로 라라벨 HTTP 클라이언트란다! Guzzle을 바탕으로 감싸진 플루언트 API 덕분에 Http::timeout(5)->retry(3, 100) 한 줄이면 네트워크 불안정 시 100ms 간격으로 3번까지 자동 재시도해 주지. 그리고 라라벨 11~13에 도입된 Context 기능을 쓰면 주문 고유 trace_id를 모든 로그와 외부 HTTP 요청 헤더에 자동으로 실어 보낼 수 있단다!”
🐶 토토: “멍멍! 테스트 코드에서는 Http::fake()를 쓰면 실제 인터넷 연결 없이도 가짜 PG사 승인 JSON을 즉석에서 돌려받을 수 있다멍!”
🎯 2. 실습 목표 및 서점 시나리오
- 라라벨
Http파사드를 이용해 안전한 타임아웃과 자동 재시도(Retry)가 적용된 외부 통신을 구현합니다. - 테스트 환경에서 실제 API 호출을 가로채는
Http::fake()모킹을 익힙니다. - 요청 수명주기 동안 주문 번호와 사용자 정보를 추적하는
Context및 전역 헬퍼를 작성합니다.
🛠️ 3. 단계별 실습 절차 (Step-by-Step)
[Step 1] TossPaymentGateway에 실제 HTTP 클라이언트 통신 탑재
app/Services/Payment/TossPaymentGateway.php의 charge 메서드를 라라벨 HTTP 클라이언트로 구현합니다:
namespace App\Services\Payment;
use App\Contracts\PaymentGatewayContract;
use App\Contracts\PaymentResult;
use Illuminate\Support\Facades\Http;
use Illuminate\Support\Facades\Log;
class TossPaymentGateway implements PaymentGatewayContract
{
public function __construct(
protected string $apiKey,
protected string $secretKey,
) {}
public function charge(int $amount, array $orderData): PaymentResult
{
try {
// 🚀 5초 타임아웃, 네트워크 실패 시 100ms 간격으로 최대 3회 자동 재시도!
$response = Http::withBasicAuth($this->secretKey, '')
->timeout(5)
->retry(3, 100)
->withHeaders([
'X-JinyShop-Trace-Id' => \Illuminate\Support\Facades\Context::get('trace_id'),
])
->post('https://api.tosspayments.com/v1/payments/confirm', [
'paymentKey' => $orderData['payment_key'] ?? 'test_pk',
'orderId' => $orderData['order_id'] ?? 'ORD-' . time(),
'amount' => $amount,
]);
if ($response->successful()) {
$data = $response->json();
return new PaymentResult(
success: true,
transactionId: $data['paymentKey'] ?? 'TX-'.time(),
amount: $amount
);
}
// PG사에서 거절된 경우
Log::warning('PG 결제 승인 거절: ' . $response->body());
return new PaymentResult(
success: false,
transactionId: '',
amount: $amount,
errorMessage: $response->json('message') ?? '결제 승인이 거절되었습니다.'
);
} catch (\Throwable $e) {
Log::error('PG사 네트워크 통신 장애: ' . $e->getMessage());
return new PaymentResult(
success: false,
transactionId: '',
amount: $amount,
errorMessage: '결제 대행사 통신 연결 실패'
);
}
}
public function refund(string $transactionId, int $amount): bool
{
return true;
}
}
[Step 2] Http::fake()로 가짜 PG사 승인 테스트 실습
실제 토스 계정이나 돈이 없어도 Http::fake()를 쓰면 단위 테스트나 로컬 시뮬레이션을 완벽히 수행할 수 있습니다:
php artisan tinker
use Illuminate\Support\Facades\Http;
// 1. 토스 API URL로 가는 요청을 가상 승인 응답으로 가로채기!
Http::fake([
'https://api.tosspayments.com/*' => Http::response([
'paymentKey' => 'REAL-LIKE-TOSS-KEY-999',
'status' => 'DONE',
'totalAmount' => 35000,
], 200),
]);
// 2. 게이트웨이 실행
$gateway = new App\Services\Payment\TossPaymentGateway('key', 'secret');
$result = $gateway->charge(35000, ['order_id' => 'ORD-123']);
echo "가상 통신 성공 여부: " . ($result->success ? '성공!' : '실패') . PHP_EOL;
echo "거래 키: " . $result->transactionId . PHP_EOL;
출력:
가상 통신 성공 여부: 성공! / 거래 키: REAL-LIKE-TOSS-KEY-999
[Step 3] Context 파사드로 주문 추적 ID 전파
Context를 사용하면 요청이 시작될 때 생성된 고유 trace_id가 모든 로그와 쿼리, 외부 API 헤더에 자동으로 각인됩니다:
use Illuminate\Support\Facades\Context;
use Illuminate\Support\Str;
// 미들웨어나 요청 시작점에서 Context 부여
Context::add('trace_id', Str::uuid()->toString());
Context::add('user_id', auth()->id() ?? 'guest');
// 이후 Log::info()를 호출하면 Context 데이터가 로그에 자동으로 함께 기록됩니다!
Log::info('도서 결제 요청 시작');
[Step 4] 전역 헬퍼 함수 작성 (app/helpers.php)
서점 전역에서 간편하게 금액을 원화로 표기하는 헬퍼 함수를 만듭니다:
app/helpers.php:
<?php
if (! function_exists('currency_krw')) {
function currency_krw(int|float $amount): string
{
return number_format($amount) . '원';
}
}
composer.json의 autoload 섹션에 파일 등록:
"autoload": {
"files": [
"app/helpers.php"
]
}
터미널에서 composer dump-autoload를 실행하면 모든 Blade 템플릿과 코드에서 currency_krw(35000)을 즉시 사용할 수 있습니다!
🔍 4. 라라벨 공식 문서 원리 심층 분석 (Deep Dive)
Http::retry()의 백오프(Exponential Backoff) 전략
Http::retry(3, 100)는 단순히 세 번 연속 찌르는 것이 아니라, 순간적인 네트워크 혼잡이 풀릴 수 있도록 100ms, 200ms, 400ms 처럼 점진적으로 대기 시간을 늘리는 지수 백오프 전략을 지원합니다.- 외부 PG사 서버의 일시적인 순단 장애 시 주문 성공률을 99.9%로 끌어올리는 비결입니다.
🐶 5. 토토의 트러블슈팅 & 주의사항
🐶 토토의 멍멍 가이드!
- 테스트할 때 실제 외부 사이트로 진짜 요청이 나가버렸어요!
테스트 시작 전에Http::preventStrayRequests()를 선언해 두면,Http::fake()로 등록되지 않은 외부 인터넷 요청이 나갈 때 즉시 에러를 터뜨려 안전을 지켜준다멍!
💡 6. 4강 자가진단 과제
Http::fake()를 사용해 400 Bad Request (“잔액 부족”) 에러 응답을 시뮬레이션하고, 게이트웨이가errorMessage를 올바르게 반환하는지 테스트하세요.- 뷰 화면에서
currency_krw(125000)헬퍼 함수를 호출하여125,000원으로 예쁘게 렌더링되는지 확인하세요.