장바구니 이탈 복구 자동화
장바구니 이탈 감지, 복구 이메일 시퀀스, 할인 인센티브, 리타겟팅, 전환 추적 시스템을 구축합니다.
장바구니 이탈: 이커머스의 가장 큰 기회
장바구니 이탈이란 무엇인가
장바구니 이탈(Cart Abandonment)은 온라인 쇼핑몰에서 고객이 상품을 장바구니에 담은 후 결제를 완료하지 않고 사이트를 떠나는 행위를 말합니다. 이는 이커머스 비즈니스에서 가장 흔하면서도 가장 큰 매출 손실 요인입니다. Baymard Institute의 연구에 따르면 전 세계 이커머스 평균 장바구니 이탈률은 약 70.19%에 달합니다. 이는 10명의 고객이 상품을 장바구니에 담아도 실제 구매를 완료하는 사람은 겨우 3명에 불과하다는 뜻입니다.
한국 시장의 경우 상황이 더 심각합니다. 모바일 쇼핑 비중이 70%를 넘는 한국에서는 작은 화면과 복잡한 결제 프로세스로 인해 이탈률이 75~82%까지 올라가는 경우가 빈번합니다. 특히 공인인증서나 ActiveX를 요구하는 결제 시스템에서 이탈률이 급격히 증가합니다. 연간 기준으로 계산하면, 월 매출 1,000만 원인 쇼핑몰의 경우 장바구니 이탈로 인한 잠재적 손실액은 연간 약 2억 8,000만 원에 달할 수 있습니다.
하지만 이것은 위기가 아니라 기회입니다. 이탈 고객은 이미 구매 의사를 표현한 고객이므로, 완전히 새로운 고객을 확보하는 것보다 전환 비용이 5~25배 낮습니다. 적절한 자동화 복구 시스템을 구축하면 이탈 고객의 10~35%를 되돌릴 수 있으며, 이는 추가 광고비 없이 순수하게 매출을 증가시키는 가장 효율적인 방법입니다. 이번 레슨에서는 WooCommerce 기반으로 장바구니 이탈을 감지하고, 자동 복구 이메일 시퀀스를 설계하고, 리타겟팅 광고와 연동하여 복구율을 극대화하는 전체 시스템을 구축합니다.
장바구니 이탈률
연간 잠재 손실액
회수 가능한 비율
최적 발송 시점
장바구니 이탈의 주요 원인 분석
효과적인 복구 전략을 세우려면 먼저 고객이 왜 이탈하는지 이해해야 합니다. 주요 원인은 다음과 같습니다:
1. 예상치 못한 추가 비용 (48%): 배송비, 세금, 수수료 등이 결제 단계에서 갑자기 추가되면 고객은 즉시 이탈합니다. 처음부터 총 비용을 투명하게 보여주는 것이 중요합니다.
2. 복잡한 결제 프로세스 (26%): 회원가입 강요, 과도한 입력 필드, 느린 페이지 로딩은 고객의 인내심을 시험합니다. 게스트 체크아웃과 간편결제 옵션을 제공하세요.
3. 보안 신뢰 부족 (25%): SSL 인증서 없음, 낯선 결제 게이트웨이, 보안 배지 부재 등은 고객의 불안감을 높입니다.
4. 단순 브라우징 (58%): 가장 높은 비율을 차지하는 이 그룹은 실제 구매 의도 없이 가격 비교나 위시리스트 대용으로 장바구니를 사용합니다. 이 그룹은 복구 이메일의 주요 타겟이 됩니다.
5. 기술적 오류 (4%): 결제 실패, 사이트 다운, 쿠폰 코드 미작동 등 기술적 문제로 인한 이탈입니다.
이탈률 감소 vs 이탈 복구: 두 가지 전략을 병행하세요
장바구니 복구 자동화는 이미 이탈한 고객을 되돌리는 "사후 전략"입니다. 하지만 더 효과적인 것은 애초에 이탈을 방지하는 "사전 전략"입니다. 결제 프로세스 간소화, 배송비 사전 표시, 진행률 표시기 추가, 게스트 체크아웃 활성화 같은 UX 개선을 먼저 진행하고, 그래도 이탈하는 고객을 위해 복구 자동화를 운영하세요. 두 전략을 병행하면 전체 전환율을 20~40% 향상시킬 수 있습니다.
이탈 감지 시스템 구축: 커스텀 DB 테이블과 세션 추적
커스텀 데이터베이스 테이블 설계
WooCommerce는 기본적으로 장바구니 이탈 데이터를 저장하지 않습니다. 따라서 커스텀 테이블을 생성하여 장바구니 상태를 실시간으로 추적해야 합니다. 이 테이블은 고객의 이메일, 장바구니 내용, 이탈 시점, 복구 이메일 발송 상태, 복구 토큰 등을 저장합니다. 플러그인 활성화 시점에 테이블을 자동으로 생성하도록 설계하면, 다른 환경에 배포할 때도 별도의 수동 작업이 필요하지 않습니다.
/* ── 커스텀 DB 테이블 생성 (플러그인 활성화 시 실행) ── */
function create_abandoned_carts_table() {
global $wpdb;
$table = $wpdb->prefix . 'abandoned_carts';
$charset = $wpdb->get_charset_collate();
$sql = "CREATE TABLE IF NOT EXISTS {$table} (
id BIGINT(20) UNSIGNED NOT NULL AUTO_INCREMENT,
session_id VARCHAR(100) NOT NULL DEFAULT '',
email VARCHAR(200) NOT NULL DEFAULT '',
customer_name VARCHAR(200) NOT NULL DEFAULT '',
phone VARCHAR(50) NOT NULL DEFAULT '',
cart_contents LONGTEXT NOT NULL,
cart_total DECIMAL(12,2) NOT NULL DEFAULT 0.00,
currency VARCHAR(10) NOT NULL DEFAULT 'KRW',
status ENUM('active','abandoned','recovered','expired')
NOT NULL DEFAULT 'active',
recovery_token VARCHAR(64) NOT NULL DEFAULT '',
recovery_emails_sent TINYINT(1) NOT NULL DEFAULT 0,
last_email_sent_at DATETIME NULL,
recovered_order_id BIGINT(20) NULL,
coupon_code VARCHAR(50) NULL,
utm_source VARCHAR(100) NULL,
utm_medium VARCHAR(100) NULL,
ip_address VARCHAR(45) NOT NULL DEFAULT '',
user_agent TEXT NULL,
created_at DATETIME NOT NULL,
updated_at DATETIME NOT NULL,
abandoned_at DATETIME NULL,
recovered_at DATETIME NULL,
PRIMARY KEY (id),
INDEX idx_email (email),
INDEX idx_status (status),
INDEX idx_token (recovery_token),
INDEX idx_created (created_at),
INDEX idx_abandoned (abandoned_at)
) {$charset};";
require_once ABSPATH . 'wp-admin/includes/upgrade.php';
dbDelta($sql);
}
register_activation_hook(__FILE__, 'create_abandoned_carts_table');
PHP 세션 추적과 실시간 장바구니 저장
장바구니 이탈을 정확히 감지하려면 고객이 장바구니에 상품을 추가하거나 수량을 변경할 때마다 실시간으로 데이터베이스에 저장해야 합니다. WooCommerce의 세션 시스템과 연동하여 로그인 사용자와 비로그인 사용자 모두를 추적할 수 있습니다. 비로그인 사용자의 경우 체크아웃 페이지에서 이메일을 입력하는 순간부터 추적이 시작됩니다. AJAX 기반으로 이메일 입력 필드의 blur 이벤트를 캡처하면, 고객이 결제 버튼을 누르기 전에도 이메일 주소를 확보할 수 있습니다.
/* ── WooCommerce 장바구니 이탈 감지 메인 클래스 ── */
class Cart_Abandonment_Tracker {
private $table;
public function __construct() {
global $wpdb;
$this->table = $wpdb->prefix . 'abandoned_carts';
/* 장바구니 변경 이벤트 후킹 */
add_action('woocommerce_cart_updated', [$this, 'track_cart']);
add_action('woocommerce_add_to_cart', [$this, 'track_cart']);
add_action('woocommerce_cart_item_removed', [$this, 'track_cart']);
add_action('woocommerce_after_cart_item_quantity_update', [$this, 'track_cart']);
/* 주문 완료 시 복구 처리 */
add_action('woocommerce_order_status_completed', [$this, 'cart_recovered']);
add_action('woocommerce_order_status_processing', [$this, 'cart_recovered']);
/* 체크아웃 이메일 캡처 (AJAX) */
add_action('wp_ajax_capture_checkout_email', [$this, 'capture_email']);
add_action('wp_ajax_nopriv_capture_checkout_email', [$this, 'capture_email']);
/* 이탈 판정 크론 (15분 간격) */
add_action('check_abandoned_carts_cron', [$this, 'process_abandoned']);
if ( ! wp_next_scheduled('check_abandoned_carts_cron') ) {
wp_schedule_event(time(), 'every_fifteen_minutes', 'check_abandoned_carts_cron');
}
/* 커스텀 크론 간격 등록 */
add_filter('cron_schedules', function($schedules) {
$schedules['every_fifteen_minutes'] = [
'interval' => 900,
'display' => '15분마다',
];
return $schedules;
});
}
/* ─── 장바구니 상태 저장 ─── */
public function track_cart() {
if ( WC()->cart->is_empty() ) return;
$email = $this->get_customer_email();
if ( ! $email ) return;
/* 장바구니 아이템 직렬화 */
$cart_data = [];
foreach ( WC()->cart->get_cart() as $key => $item ) {
$product = $item['data'];
$thumb_id = $product->get_image_id();
$cart_data[] = [
'cart_item_key' => $key,
'product_id' => $item['product_id'],
'variation_id' => $item['variation_id'] ?? 0,
'name' => $product->get_name(),
'sku' => $product->get_sku(),
'price' => (float) $product->get_price(),
'quantity' => $item['quantity'],
'subtotal' => (float) $item['line_subtotal'],
'image' => $thumb_id ? wp_get_attachment_url($thumb_id) : '',
'url' => get_permalink($item['product_id']),
];
}
global $wpdb;
$existing = $wpdb->get_row( $wpdb->prepare(
"SELECT id FROM {$this->table}
WHERE email = %s AND status = 'active'",
$email
));
$now = current_time('mysql');
$data = [
'email' => $email,
'customer_name' => $this->get_customer_name(),
'phone' => $this->get_customer_phone(),
'cart_contents' => wp_json_encode($cart_data),
'cart_total' => (float) WC()->cart->get_total('edit'),
'currency' => get_woocommerce_currency(),
'status' => 'active',
'updated_at' => $now,
'ip_address' => $this->get_client_ip(),
'user_agent' => sanitize_text_field($_SERVER['HTTP_USER_AGENT'] ?? ''),
];
if ( $existing ) {
$wpdb->update($this->table, $data, ['id' => $existing->id]);
} else {
$data['session_id'] = WC()->session->get_customer_id();
$data['recovery_token'] = wp_generate_uuid4();
$data['created_at'] = $now;
$wpdb->insert($this->table, $data);
}
}
/* ─── 이탈 판정 및 웹훅 트리거 ─── */
public function process_abandoned() {
global $wpdb;
$abandoned = $wpdb->get_results(
"SELECT * FROM {$this->table}
WHERE status = 'active'
AND updated_at < DATE_SUB(NOW(), INTERVAL 1 HOUR)
AND recovery_emails_sent = 0"
);
foreach ( $abandoned as $cart ) {
$wpdb->update(
$this->table,
['status' => 'abandoned', 'abandoned_at' => current_time('mysql')],
['id' => $cart->id]
);
/* Make.com 웹훅으로 복구 시퀀스 시작 */
wp_remote_post( get_option('cart_recovery_webhook_url', ''), [
'body' => wp_json_encode([
'event' => 'cart_abandoned',
'cart_id' => (int) $cart->id,
'email' => $cart->email,
'name' => $cart->customer_name,
'phone' => $cart->phone,
'cart_total' => (float) $cart->cart_total,
'currency' => $cart->currency,
'items' => json_decode($cart->cart_contents, true),
'recovery_url' => home_url('/recover-cart/?token=' . $cart->recovery_token),
'abandoned_at' => current_time('c'),
]),
'headers' => ['Content-Type' => 'application/json'],
'timeout' => 15,
]);
}
}
/* ─── 주문 완료 → 복구 기록 ─── */
public function cart_recovered( $order_id ) {
$order = wc_get_order($order_id);
if ( ! $order ) return;
global $wpdb;
$wpdb->update(
$this->table,
[
'status' => 'recovered',
'recovered_order_id' => $order_id,
'recovered_at' => current_time('mysql'),
],
['email' => $order->get_billing_email(), 'status' => 'abandoned']
);
}
/* ─── AJAX 이메일 캡처 ─── */
public function capture_email() {
check_ajax_referer('capture_email_nonce', 'nonce');
$email = sanitize_email($_POST['email'] ?? '');
if ( is_email($email) && WC()->session ) {
WC()->session->set('billing_email', $email);
$this->track_cart();
}
wp_send_json_success();
}
/* ─── 헬퍼 메서드 ─── */
private function get_customer_email() {
if ( is_user_logged_in() ) return wp_get_current_user()->user_email;
return WC()->session ? WC()->session->get('billing_email') : null;
}
private function get_customer_name() {
if ( is_user_logged_in() ) {
$u = wp_get_current_user();
return trim($u->first_name . ' ' . $u->last_name);
}
return WC()->session ? WC()->session->get('billing_first_name') : '';
}
private function get_customer_phone() {
return WC()->session ? WC()->session->get('billing_phone') : '';
}
private function get_client_ip() {
foreach (['HTTP_CF_CONNECTING_IP','HTTP_X_FORWARDED_FOR','REMOTE_ADDR'] as $h) {
if ( ! empty($_SERVER[$h]) ) return sanitize_text_field($_SERVER[$h]);
}
return '0.0.0.0';
}
}
new Cart_Abandonment_Tracker();
비로그인 사용자 이메일 캡처가 핵심입니다
장바구니 이탈 고객의 약 60%는 비로그인 상태입니다. 이메일을 확보하지 못하면 복구 이메일을 보낼 수 없습니다. 체크아웃 페이지에서 이메일 입력 필드에 blur 이벤트를 걸어 AJAX로 즉시 캡처하세요. 고객이 이메일만 입력하고 이탈해도 복구 시퀀스를 시작할 수 있습니다. 아래 JavaScript 코드를 체크아웃 페이지에 추가하면 됩니다.
/* ── 체크아웃 이메일 실시간 캡처 (프론트엔드 JS) ── */
jQuery(function($) {
var emailCaptured = false;
// 이메일 필드 blur 이벤트로 즉시 캡처
$('#billing_email').on('blur change', function() {
var email = $(this).val().trim();
if ( email && isValidEmail(email) && !emailCaptured ) {
$.ajax({
url: wc_checkout_params.ajax_url,
type: 'POST',
data: {
action: 'capture_checkout_email',
email: email,
nonce: cartRecoveryData.nonce
},
success: function() {
emailCaptured = true;
console.log('[Cart Recovery] 이메일 캡처 성공:', email);
}
});
}
});
function isValidEmail(email) {
return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email);
}
});
/* ── PHP: 프론트엔드 스크립트 등록 ── */
add_action('wp_enqueue_scripts', function() {
if ( is_checkout() ) {
wp_enqueue_script(
'cart-recovery-capture',
plugin_dir_url(__FILE__) . 'js/email-capture.js',
['jquery'], '1.0', true
);
wp_localize_script('cart-recovery-capture', 'cartRecoveryData', [
'nonce' => wp_create_nonce('capture_email_nonce'),
]);
}
});
무료 가입하면 이어서 볼 수 있고, 강의를 완료할 때마다 XP와 레벨이 쌓입니다.