PHP 택배API 연동 가이드
이 가이드에서 다루는 내용
• cURL을 사용한 API 호출
• Guzzle HTTP 클라이언트 예제
• Laravel 웹훅 컨트롤러
• 에러 처리 패턴
• cURL을 사용한 API 호출
• Guzzle HTTP 클라이언트 예제
• Laravel 웹훅 컨트롤러
• 에러 처리 패턴
1. 설치 (Guzzle 사용 시)
composer require guzzlehttp/guzzle cURL만 사용한다면 추가 설치 없이 PHP 기본 기능으로 가능합니다.
2. 환경 변수 설정
# .env 파일
DELIVERY_PUBLIC_KEY=pk_live_your_public_key
DELIVERY_SECRET_KEY=sk_live_your_secret_key
WEBHOOK_SECRET=your_webhook_secret <?php
// PHP 기본
$publicKey = getenv('DELIVERY_PUBLIC_KEY');
$secretKey = getenv('DELIVERY_SECRET_KEY');
// Laravel
$publicKey = env('DELIVERY_PUBLIC_KEY');
$secretKey = env('DELIVERY_SECRET_KEY'); 보안 주의
Secret Key를 코드에 직접 하드코딩하지 마세요. 환경 변수를 사용하세요.
Secret Key를 코드에 직접 하드코딩하지 마세요. 환경 변수를 사용하세요.
3. 배송 조회 (cURL)
기본 예제
<?php
define('PUBLIC_KEY', getenv('DELIVERY_PUBLIC_KEY'));
define('SECRET_KEY', getenv('DELIVERY_SECRET_KEY'));
define('BASE_URL', 'https://api.deliveryapi.co.kr/v1');
function trackDelivery(string $courierCode, string $trackingNumber): array
{
$url = BASE_URL . '/tracking/trace';
$payload = json_encode([
'items' => [[
'courierCode' => $courierCode,
'trackingNumber' => $trackingNumber
]]
]);
$ch = curl_init();
curl_setopt_array($ch, [
CURLOPT_URL => $url,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_POST => true,
CURLOPT_POSTFIELDS => $payload,
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ' . PUBLIC_KEY . ':' . SECRET_KEY,
'Content-Type: application/json'
]
]);
$response = curl_exec($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
if ($httpCode !== 200) {
throw new Exception('API Error: ' . $response);
}
$data = json_decode($response, true);
return $data['data']['results'][0]; // 첫 번째 결과 반환
}
// 사용 예시
try {
$result = trackDelivery('cj', '1234567890');
if ($result['success']) {
$data = $result['data'];
echo '배송 상태: ' . $data['deliveryStatus'] . PHP_EOL;
echo '택배사: ' . $data['courierName'] . PHP_EOL;
} else {
echo '조회 실패: ' . $result['message'] . PHP_EOL;
}
// 배송 이력 출력
foreach ($result['data']['progresses'] as $progress) {
echo " ${$progress['dateTime']} - ${$progress['description']}" . PHP_EOL;
}
} catch (Exception $e) {
echo '에러: ' . $e->getMessage();
} 4. Guzzle 사용 예제
<?php
require 'vendor/autoload.php';
use GuzzleHttp\Client;
use GuzzleHttp\Exception\RequestException;
class DeliveryApiClient
{
private Client $client;
private string $publicKey;
private string $secretKey;
private string $baseUrl = 'https://api.deliveryapi.co.kr/v1';
public function __construct(string $publicKey, string $secretKey)
{
$this->publicKey = $publicKey;
$this->secretKey = $secretKey;
$this->client = new Client([
'base_uri' => $this->baseUrl,
'headers' => [
'Authorization' => 'Bearer ' . $publicKey . ':' . $secretKey,
'Content-Type' => 'application/json'
]
]);
}
public function track(string $courierCode, string $trackingNumber): array
{
try {
$response = $this->client->post('/tracking/trace', [
'json' => [
'items' => [[
'courierCode' => $courierCode,
'trackingNumber' => $trackingNumber
]]
]
]);
$data = json_decode($response->getBody(), true);
return $data['data']['results'][0]; // 첫 번째 결과 반환
} catch (RequestException $e) {
$errorBody = $e->getResponse()?->getBody()->getContents();
throw new Exception('API Error: ' . $errorBody);
}
}
public function getCouriers(): array
{
$response = $this->client->get('/tracking/couriers');
return json_decode($response->getBody(), true);
}
}
// 사용 예시
$api = new DeliveryApiClient(
getenv('DELIVERY_PUBLIC_KEY'),
getenv('DELIVERY_SECRET_KEY')
);
$result = $api->track('cj', '1234567890');
print_r($result['data']); 5. 택배사 목록 조회
<?php
function getCouriers(): array
{
$url = BASE_URL . '/tracking/couriers';
$ch = curl_init();
curl_setopt_array($ch, [
CURLOPT_URL => $url,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ' . PUBLIC_KEY . ':' . SECRET_KEY
]
]);
$response = curl_exec($ch);
curl_close($ch);
return json_decode($response, true);
}
// 결과 예시
$couriers = getCouriers();
foreach ($couriers['data'] as $courier) {
echo "${$courier['code']}: ${$courier['name']}" . PHP_EOL;
}
// lotte: 롯데택배
// cj: CJ대한통운
// hanjin: 한진택배 6. Laravel 웹훅 컨트롤러
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use Illuminate\Http\JsonResponse;
use Illuminate\Support\Facades\Log;
class DeliveryWebhookController extends Controller
{
public function handle(Request $request): JsonResponse
{
// 서명 검증
$signature = $request->header('X-Webhook-Signature');
$timestamp = $request->header('X-Webhook-Timestamp');
$payload = $request->getContent();
if (!$this->verifySignature($payload, $signature, $timestamp)) {
return response()->json(['error' => 'Invalid signature'], 401);
}
$data = $request->json();
$event = $data->get('event');
$items = $data->get('items');
foreach ($items as $item) {
switch ($event) {
case 'tracking.polled':
$this->handleStatusChanged($item);
break;
case 'tracking.completed':
$this->handleDelivered($item);
break;
}
}
return response()->json(['received' => true]);
}
private function verifySignature(string $payload, ?string $signature, ?string $timestamp): bool
{
if (!$signature || !$timestamp) {
return false;
}
// timestamp 유효성 검증 (5분 허용)
if (abs(time() - (int) $timestamp) > 300) {
return false;
}
// 서명 대상: timestamp.payload
$secret = config('services.delivery.webhook_secret');
$expected = hash_hmac('sha256', $timestamp . '.' . $payload, $secret);
// 헤더값: sha256={hex} → prefix 제거 후 비교
$received = str_replace('sha256=', '', $signature);
return hash_equals($expected, $received);
}
private function handleStatusChanged(array $data): void
{
Log::info('배송 상태 변경', [
'trackingNumber' => $data['trackingNumber'],
'status' => $data['currentStatus']
]);
// 비즈니스 로직 처리
// 예: 주문 상태 업데이트, 고객 알림 등
}
private function handleDelivered(array $data): void
{
Log::info('배송 완료', [
'trackingNumber' => $data['trackingNumber']
]);
// 배송 완료 처리
}
}
// routes/api.php
Route::post('/webhook/delivery', [DeliveryWebhookController::class, 'handle'])
->withoutMiddleware([\App\Http\Middleware\VerifyCsrfToken::class]); 7. 에러 처리 및 재시도
<?php
function trackWithRetry(string $courier, string $trackingNumber, int $maxRetries = 3): array
{
$lastException = null;
for ($attempt = 1; $attempt <= $maxRetries; $attempt++) {
try {
return trackDelivery($courier, $trackingNumber);
} catch (Exception $e) {
$lastException = $e;
// 재시도 가능 여부 확인
if (isRetryable($e) && $attempt < $maxRetries) {
$delay = pow(2, $attempt); // 지수 백오프 (초)
echo "재시도 ${$attempt}/${$maxRetries}, ${$delay}초 후..." . PHP_EOL;
sleep($delay);
continue;
}
throw $e;
}
}
throw $lastException;
}
function isRetryable(Exception $e): bool
{
$message = $e->getMessage();
// 서버 에러 또는 네트워크 에러
return strpos($message, '5') !== false
|| strpos($message, 'timeout') !== false
|| strpos($message, 'connect') !== false;
}
// 에러 코드별 처리
function handleApiError(string $errorResponse): string
{
$error = json_decode($errorResponse, true);
$code = $error['code'] ?? '';
$messages = [
'INVALID_TRACKING_NUMBER' => '잘못된 송장번호입니다',
'COURIER_NOT_SUPPORTED' => '지원하지 않는 택배사입니다',
'RATE_LIMIT_EXCEEDED' => '요청 한도를 초과했습니다',
'UNAUTHORIZED' => 'API 키가 유효하지 않습니다',
];
return $messages[$code] ?? '일시적인 오류가 발생했습니다';
} 8. WordPress 플러그인 예제
<?php
/**
* Plugin Name: Delivery Tracker
* Description: 택배 조회 기능
*/
class DeliveryTracker
{
private string $apiKey;
public function __construct()
{
$this->apiKey = get_option('delivery_api_key');
add_shortcode('delivery_track', [$this, 'renderTrackingForm']);
add_action('wp_ajax_track_delivery', [$this, 'ajaxTrack']);
add_action('wp_ajax_nopriv_track_delivery', [$this, 'ajaxTrack']);
}
public function renderTrackingForm(): string
{
ob_start();
?>
<form id="delivery-form">
<select name="courier">
<option value="lotte">롯데택배</option>
<option value="cj">CJ대한통운</option>
<option value="hanjin">한진택배</option>
</select>
<input type="text" name="tracking_number" placeholder="송장번호">
<button type="submit">조회</button>
</form>
<div id="delivery-result"></div>
<?php
return ob_get_clean();
}
public function ajaxTrack(): void
{
$courier = sanitize_text_field($_POST['courier']);
$trackingNumber = sanitize_text_field($_POST['tracking_number']);
try {
$result = $this->track($courier, $trackingNumber);
wp_send_json_success($result['data']);
} catch (Exception $e) {
wp_send_json_error($e->getMessage());
}
}
private function track(string $courier, string $trackingNumber): array
{
// cURL 호출 로직 (위 예제 참고)
}
}