security: KVE-2026-2345 장바구니 옵션 검증으로 상품대금 누락 차단
공통·기본 테마 AJAX와 일반 장바구니 요청에 동일한 상품·옵션 검증을 적용한다. 주문 준비 및 PC·모바일 주문 제출에서도 기존 행의 종류·가격·구성을 확인한다. 공통 규칙 46개와 격리 MySQL의 MyISAM·InnoDB HTTP/DB 검증 126개를 통과했다. 주문 검증은 PG·주문 저장 이전 경계까지이며 전체 결제 및 동시성 검증은 별도로 필요하다.
This commit is contained in:
@@ -0,0 +1,159 @@
|
||||
<?php
|
||||
if (!defined('_GNUBOARD_')) exit;
|
||||
|
||||
// KVE-2026-2345: 요청의 옵션 종류는 가격 계산의 근거가 될 수 없다.
|
||||
// DB 조회와 분리하여 신규 요청과 기존 장바구니에 같은 규칙을 적용한다.
|
||||
function shop_validate_cart_rows($item, $options, $rows, $check_price = false)
|
||||
{
|
||||
$error = '상품 또는 옵션 정보가 올바르지 않습니다. 장바구니에서 삭제한 뒤 다시 담아 주십시오.';
|
||||
if (empty($item['it_id']) || empty($item['it_use']) || !empty($item['it_tel_inq']) || !$rows)
|
||||
return array('error' => $error);
|
||||
|
||||
$option_map = array();
|
||||
$has_selection = false;
|
||||
foreach ($options as $option) {
|
||||
if ((string) $option['it_id'] !== (string) $item['it_id'])
|
||||
return array('error' => $error);
|
||||
if ((string) $option['io_type'] === '0')
|
||||
$has_selection = true;
|
||||
$key = $option['io_type'].':'.$option['io_id'];
|
||||
if (isset($option_map[$key]))
|
||||
return array('error' => $error);
|
||||
$option_map[$key] = $option;
|
||||
}
|
||||
|
||||
$validated = array();
|
||||
$quantities = array();
|
||||
$base_qty = 0;
|
||||
$total = 0;
|
||||
foreach ($rows as $row) {
|
||||
if (!isset($row['io_id'], $row['io_type'], $row['ct_qty']) ||
|
||||
!is_string($row['io_id']) ||
|
||||
!(is_string($row['io_type']) || is_int($row['io_type'])) ||
|
||||
!in_array((string) $row['io_type'], array('0', '1'), true) ||
|
||||
!(is_string($row['ct_qty']) || is_int($row['ct_qty'])) ||
|
||||
!preg_match('/^[1-9][0-9]*$/D', (string) $row['ct_qty']) ||
|
||||
(float) $row['ct_qty'] > 2147483647)
|
||||
return array('error' => $error);
|
||||
|
||||
$type = (int) $row['io_type'];
|
||||
if (!$validated && $type !== 0)
|
||||
return array('error' => $error);
|
||||
$id = $row['io_id'];
|
||||
$key = $type.':'.$id;
|
||||
if ($id === '') {
|
||||
if ($type !== 0 || $has_selection)
|
||||
return array('error' => $error);
|
||||
$price = 0;
|
||||
$stock = (int) $item['it_stock_qty'];
|
||||
} else {
|
||||
if (!isset($option_map[$key]) || empty($option_map[$key]['io_use']))
|
||||
return array('error' => $error);
|
||||
$option = $option_map[$key];
|
||||
$type = (int) $option['io_type'];
|
||||
$price = (int) $option['io_price'];
|
||||
$stock = (int) $option['io_stock_qty'];
|
||||
}
|
||||
|
||||
$qty = (int) $row['ct_qty'];
|
||||
$quantities[$key] = isset($quantities[$key]) ? $quantities[$key] + $qty : $qty;
|
||||
if ($quantities[$key] > $stock)
|
||||
return array('error' => '상품 또는 옵션의 재고수량이 부족합니다.');
|
||||
$unit_price = $type === 1 ? $price : (int) $item['it_price'] + $price;
|
||||
if ($unit_price < 0)
|
||||
return array('error' => $error);
|
||||
if ($check_price && (!isset($row['ct_price'], $row['io_price']) ||
|
||||
(int) $row['ct_price'] !== (int) $item['it_price'] || (int) $row['io_price'] !== $price))
|
||||
return array('error' => '상품 또는 옵션 금액이 변경되었습니다. 장바구니를 다시 확인해 주십시오.');
|
||||
|
||||
if ($type === 0)
|
||||
$base_qty += $qty;
|
||||
$row['io_type'] = $type;
|
||||
$row['io_price'] = $price;
|
||||
$row['ct_price'] = (int) $item['it_price'];
|
||||
$row['ct_qty'] = $qty;
|
||||
$validated[] = $row;
|
||||
$total += $unit_price * $qty;
|
||||
}
|
||||
|
||||
if (!$base_qty || (!empty($item['it_buy_min_qty']) && $base_qty < $item['it_buy_min_qty']) ||
|
||||
(!empty($item['it_buy_max_qty']) && $base_qty > $item['it_buy_max_qty']))
|
||||
return array('error' => '상품의 선택옵션과 최소/최대 구매수량을 확인해 주십시오.');
|
||||
|
||||
return array('error' => '', 'rows' => $validated, 'total' => $total);
|
||||
}
|
||||
|
||||
function shop_cart_option_data($it_id)
|
||||
{
|
||||
global $g5;
|
||||
$id = sql_escape_string($it_id);
|
||||
$item = sql_fetch(" select * from {$g5['g5_shop_item_table']} where it_id = '$id' ");
|
||||
$result = sql_query(" select * from {$g5['g5_shop_item_option_table']} where it_id = '$id' ");
|
||||
$options = array();
|
||||
while ($row = sql_fetch_array($result))
|
||||
$options[] = $row;
|
||||
return array($item, $options);
|
||||
}
|
||||
|
||||
// 모든 상품을 먼저 검증한다. 바로구매/옵션수정의 선삭제와 다중 상품의 부분 저장을 막는다.
|
||||
function shop_validate_cart_request($post, $multi = false)
|
||||
{
|
||||
$error = '장바구니 요청 정보가 올바르지 않습니다.';
|
||||
$products = array();
|
||||
if (empty($post['it_id']) || !is_array($post['it_id']) ||
|
||||
array_keys($post['it_id']) !== range(0, count($post['it_id']) - 1))
|
||||
return array('error' => $error);
|
||||
foreach ($post['it_id'] as $i => $id) {
|
||||
if ($multi && empty($post['chk_it_id'][$i]))
|
||||
continue;
|
||||
if (!is_string($id) || $id === '' || safe_replace_regex($id, 'it_id') !== $id || isset($products[$id]))
|
||||
return array('error' => $error);
|
||||
foreach (array('io_id', 'io_type', 'ct_qty') as $field) {
|
||||
if (!isset($post[$field][$id]) || !is_array($post[$field][$id]) || !$post[$field][$id])
|
||||
return array('error' => $error);
|
||||
}
|
||||
$keys = range(0, count($post['io_id'][$id]) - 1);
|
||||
foreach (array('io_id', 'io_type', 'ct_qty') as $field) {
|
||||
if (array_keys($post[$field][$id]) !== $keys)
|
||||
return array('error' => $error);
|
||||
}
|
||||
$rows = array();
|
||||
foreach ($keys as $k) {
|
||||
$io_id = $post['io_id'][$id][$k];
|
||||
if (!is_string($io_id) || preg_replace(G5_OPTION_ID_FILTER, '', $io_id) !== $io_id ||
|
||||
(isset($post['io_value'][$id][$k]) && !is_string($post['io_value'][$id][$k])))
|
||||
return array('error' => $error);
|
||||
$rows[] = array('io_id' => $io_id, 'io_type' => $post['io_type'][$id][$k], 'ct_qty' => $post['ct_qty'][$id][$k]);
|
||||
}
|
||||
list($item, $options) = shop_cart_option_data($id);
|
||||
$validated = shop_validate_cart_rows($item, $options, $rows);
|
||||
if ($validated['error'] !== '')
|
||||
return $validated;
|
||||
$validated['item'] = $item;
|
||||
$products[$id] = $validated;
|
||||
}
|
||||
return array('error' => $products ? '' : $error, 'products' => $products);
|
||||
}
|
||||
|
||||
// 주문 준비/확정 시에는 선택된 행만 검증하여 보조옵션 단독 선택과 과거 조작 행도 차단한다.
|
||||
function shop_validate_order_cart($cart_id)
|
||||
{
|
||||
global $g5;
|
||||
$id = sql_escape_string($cart_id);
|
||||
$result = sql_query(" select * from {$g5['g5_shop_cart_table']} where od_id = '$id' and ct_select = '1' order by ct_id asc ");
|
||||
$products = array();
|
||||
while ($row = sql_fetch_array($result)) {
|
||||
if ($row['ct_status'] !== '쇼핑')
|
||||
return '이미 처리되었거나 올바르지 않은 장바구니입니다.';
|
||||
$products[$row['it_id']][] = $row;
|
||||
}
|
||||
if (!$products)
|
||||
return '주문하실 상품을 선택해 주십시오.';
|
||||
foreach ($products as $it_id => $rows) {
|
||||
list($item, $options) = shop_cart_option_data($it_id);
|
||||
$validated = shop_validate_cart_rows($item, $options, $rows, true);
|
||||
if ($validated['error'] !== '')
|
||||
return $validated['error'];
|
||||
}
|
||||
return '';
|
||||
}
|
||||
+2
-1
@@ -1,4 +1,5 @@
|
||||
<?php
|
||||
include_once(dirname(__FILE__).'/shop.cartvalidate.lib.php');
|
||||
//==============================================================================
|
||||
// 쇼핑몰 라이브러리 모음 시작
|
||||
//==============================================================================
|
||||
@@ -2694,7 +2695,7 @@ function before_check_cart_price($s_cart_id, $is_ct_select_condition=false, $is_
|
||||
}
|
||||
|
||||
if( $row['io_id'] ){
|
||||
$io_sql = " select * from {$g5['g5_shop_item_option_table']} where it_id = '{$it['it_id']}' and io_id = '{$row['io_id']}' ";
|
||||
$io_sql = " select * from {$g5['g5_shop_item_option_table']} where it_id = '{$it['it_id']}' and io_id = '{$row['io_id']}' and io_type = '{$row['io_type']}' and io_use = '1' ";
|
||||
$io_infos = sql_fetch( $io_sql );
|
||||
|
||||
if( $io_infos['io_type'] ){
|
||||
|
||||
@@ -71,6 +71,12 @@ if (get_cart_count($tmp_cart_id) == 0) { // 장바구니에 담기
|
||||
alert('장바구니가 비어 있습니다.\\n\\n이미 주문하셨거나 장바구니에 담긴 상품이 없는 경우입니다.', G5_SHOP_URL.'/cart.php');
|
||||
}
|
||||
|
||||
// KVE-2026-2345: 과거 장바구니의 옵션 종류와 가격도 주문 전에 다시 검증한다.
|
||||
$cart_validation_error = shop_validate_order_cart($tmp_cart_id);
|
||||
if ($cart_validation_error !== '') {
|
||||
alert($cart_validation_error, G5_SHOP_URL.'/cart.php');
|
||||
}
|
||||
|
||||
// 변수 초기화
|
||||
$od_other_pay_type = '';
|
||||
|
||||
|
||||
+13
-33
@@ -78,35 +78,20 @@ switch ($action) {
|
||||
if ($count < 1)
|
||||
die(json_encode(array('error' => '장바구니에 담을 상품을 선택하여 주십시오.')));
|
||||
|
||||
$cart_validation = shop_validate_cart_request($_POST);
|
||||
if ($cart_validation['error'] !== '')
|
||||
die(json_encode(array('error' => $cart_validation['error'])));
|
||||
|
||||
$ct_count = 0;
|
||||
for($i=0; $i<$count; $i++) {
|
||||
$it_id = isset($_POST['it_id'][$i]) ? safe_replace_regex($_POST['it_id'][$i], 'it_id') : '';
|
||||
$opt_count = (isset($_POST['io_id'][$it_id]) && is_array($_POST['io_id'][$it_id])) ? count($_POST['io_id'][$it_id]) : 0;
|
||||
|
||||
// 상품정보
|
||||
$it = get_shop_item($it_id, false);
|
||||
$it = $cart_validation['products'][$it_id]['item'];
|
||||
if(!$it['it_id'])
|
||||
die(json_encode(array('error' => '상품정보가 존재하지 않습니다.')));
|
||||
|
||||
// 옵션정보를 얻어서 배열에 저장
|
||||
$opt_list = array();
|
||||
$sql = " select * from {$g5['g5_shop_item_option_table']} where it_id = '$it_id' order by io_no asc ";
|
||||
$result = sql_query($sql);
|
||||
$lst_count = 0;
|
||||
for($k=0; $row=sql_fetch_array($result); $k++) {
|
||||
$opt_list[$row['io_type']][$row['io_id']]['id'] = $row['io_id'];
|
||||
$opt_list[$row['io_type']][$row['io_id']]['use'] = $row['io_use'];
|
||||
$opt_list[$row['io_type']][$row['io_id']]['price'] = $row['io_price'];
|
||||
$opt_list[$row['io_type']][$row['io_id']]['stock'] = $row['io_stock_qty'];
|
||||
|
||||
// 선택옵션 개수
|
||||
if(!$row['io_type'])
|
||||
$lst_count++;
|
||||
}
|
||||
|
||||
if($lst_count > 0 && !trim($_POST['io_id'][$it_id][$i]) && $_POST['io_type'][$it_id][$i] == 0)
|
||||
die(json_encode(array('error' => '상품의 선택옵션을 선택해 주십시오.')));
|
||||
|
||||
for($k=0; $k<$opt_count; $k++) {
|
||||
$post_ct_qty = isset($_POST['ct_qty'][$it_id][$k]) ? (int) $_POST['ct_qty'][$it_id][$k] : 0;
|
||||
if ($post_ct_qty < 1)
|
||||
@@ -155,25 +140,17 @@ switch ($action) {
|
||||
|
||||
// 장바구니에 Insert
|
||||
$comma = '';
|
||||
$ct_count = 0;
|
||||
$sql = " INSERT INTO {$g5['g5_shop_cart_table']}
|
||||
( od_id, mb_id, it_id, it_name, it_sc_type, it_sc_method, it_sc_price, it_sc_minimum, it_sc_qty, ct_status, ct_price, ct_point, ct_point_use, ct_stock_use, ct_option, ct_qty, ct_notax, io_id, io_type, io_price, ct_time, ct_ip, ct_send_cost, ct_direct, ct_select, ct_select_time )
|
||||
VALUES ";
|
||||
|
||||
for($k=0; $k<$opt_count; $k++) {
|
||||
$io_id = isset($_POST['io_id'][$it_id][$k]) ? preg_replace(G5_OPTION_ID_FILTER, '', $_POST['io_id'][$it_id][$k]) : '';
|
||||
$io_type = isset($_POST['io_type'][$it_id][$k]) ? preg_replace('#[^01]#', '', $_POST['io_type'][$it_id][$k]) : '';
|
||||
$io_id = $cart_validation['products'][$it_id]['rows'][$k]['io_id'];
|
||||
$io_type = $cart_validation['products'][$it_id]['rows'][$k]['io_type'];
|
||||
$io_value = isset($_POST['io_value'][$it_id][$k]) ? $_POST['io_value'][$it_id][$k] : '';
|
||||
|
||||
// 선택옵션정보가 존재하는데 선택된 옵션이 없으면 건너뜀
|
||||
if($lst_count && $io_id == '')
|
||||
continue;
|
||||
|
||||
$opt_list_type_id_use = isset($opt_list[$io_type][$io_id]['use']) ? $opt_list[$io_type][$io_id]['use'] : '';
|
||||
// 구매할 수 없는 옵션은 건너뜀
|
||||
if($io_id && ! $opt_list_type_id_use)
|
||||
continue;
|
||||
|
||||
$io_price = isset($opt_list[$io_type][$io_id]['price']) ? $opt_list[$io_type][$io_id]['price'] : 0;
|
||||
$io_price = $cart_validation['products'][$it_id]['rows'][$k]['io_price'];
|
||||
$ct_qty = isset($_POST['ct_qty'][$it_id][$k]) ? (int) $_POST['ct_qty'][$it_id][$k] : 0;
|
||||
|
||||
// 구매가격이 음수인지 체크
|
||||
@@ -186,14 +163,17 @@ switch ($action) {
|
||||
}
|
||||
|
||||
// 동일옵션의 상품이 있으면 수량 더함
|
||||
$sql2 = " select ct_id, io_type, ct_qty
|
||||
$sql2 = " select ct_id, io_type, ct_qty, ct_price, io_price
|
||||
from {$g5['g5_shop_cart_table']}
|
||||
where od_id = '$tmp_cart_id'
|
||||
and it_id = '$it_id'
|
||||
and io_id = '$io_id'
|
||||
and io_type = '$io_type'
|
||||
and ct_status = '쇼핑' ";
|
||||
$row2 = sql_fetch($sql2);
|
||||
if(isset($row2['ct_id']) && $row2['ct_id']) {
|
||||
if ((int) $row2['ct_price'] !== (int) $it['it_price'] || (int) $row2['io_price'] !== $io_price)
|
||||
die(json_encode(array('error' => '기존 장바구니의 상품 정보가 변경되었습니다. 삭제한 뒤 다시 담아 주십시오.')));
|
||||
// 재고체크
|
||||
$tmp_ct_qty = $row2['ct_qty'];
|
||||
if(!$io_id)
|
||||
|
||||
+15
-31
@@ -146,6 +146,10 @@ else // 장바구니에 담기
|
||||
if ($count < 1)
|
||||
alert('장바구니에 담을 상품을 선택하여 주십시오.');
|
||||
|
||||
$cart_validation = shop_validate_cart_request($_POST, $act === 'multi');
|
||||
if ($cart_validation['error'] !== '')
|
||||
alert($cart_validation['error']);
|
||||
|
||||
$ct_count = 0;
|
||||
$post_chk_it_id = (isset($_POST['chk_it_id']) && is_array($_POST['chk_it_id'])) ? $_POST['chk_it_id'] : array();
|
||||
$post_io_ids = (isset($_POST['io_id']) && is_array($_POST['io_id'])) ? $_POST['io_id'] : array();
|
||||
@@ -184,7 +188,7 @@ else // 장바구니에 담기
|
||||
}
|
||||
|
||||
// 상품정보
|
||||
$it = get_shop_item($it_id, false);
|
||||
$it = $cart_validation['products'][$it_id]['item'];
|
||||
if(!$it['it_id'])
|
||||
alert('상품정보가 존재하지 않습니다.');
|
||||
|
||||
@@ -219,30 +223,14 @@ else // 장바구니에 담기
|
||||
}
|
||||
}
|
||||
|
||||
// 옵션정보를 얻어서 배열에 저장
|
||||
$opt_list = array();
|
||||
$sql = " select * from {$g5['g5_shop_item_option_table']} where it_id = '$it_id' and io_use = 1 order by io_no asc ";
|
||||
$result = sql_query($sql);
|
||||
$lst_count = 0;
|
||||
for($k=0; $row=sql_fetch_array($result); $k++) {
|
||||
$opt_list[$row['io_type']][$row['io_id']]['id'] = $row['io_id'];
|
||||
$opt_list[$row['io_type']][$row['io_id']]['use'] = $row['io_use'];
|
||||
$opt_list[$row['io_type']][$row['io_id']]['price'] = $row['io_price'];
|
||||
$opt_list[$row['io_type']][$row['io_id']]['stock'] = $row['io_stock_qty'];
|
||||
|
||||
// 선택옵션 개수
|
||||
if(!$row['io_type'])
|
||||
$lst_count++;
|
||||
}
|
||||
|
||||
//--------------------------------------------------------
|
||||
// 재고 검사, 바로구매일 때만 체크
|
||||
//--------------------------------------------------------
|
||||
// 이미 주문폼에 있는 같은 상품의 수량합계를 구한다.
|
||||
if($sw_direct) {
|
||||
for($k=0; $k<$opt_count; $k++) {
|
||||
$io_id = isset($_POST['io_id'][$it_id][$k]) ? preg_replace(G5_OPTION_ID_FILTER, '', $_POST['io_id'][$it_id][$k]) : '';
|
||||
$io_type = isset($_POST['io_type'][$it_id][$k]) ? preg_replace('#[^01]#', '', $_POST['io_type'][$it_id][$k]) : '';
|
||||
$io_id = $cart_validation['products'][$it_id]['rows'][$k]['io_id'];
|
||||
$io_type = $cart_validation['products'][$it_id]['rows'][$k]['io_type'];
|
||||
$io_value = isset($_POST['io_value'][$it_id][$k]) ? $_POST['io_value'][$it_id][$k] : '';
|
||||
|
||||
$sql = " select SUM(ct_qty) as cnt from {$g5['g5_shop_cart_table']}
|
||||
@@ -287,24 +275,17 @@ else // 장바구니에 담기
|
||||
|
||||
// 장바구니에 Insert
|
||||
$comma = '';
|
||||
$ct_count = 0;
|
||||
$sql = " INSERT INTO {$g5['g5_shop_cart_table']}
|
||||
( od_id, mb_id, it_id, it_name, it_sc_type, it_sc_method, it_sc_price, it_sc_minimum, it_sc_qty, ct_status, ct_price, ct_point, ct_point_use, ct_stock_use, ct_option, ct_qty, ct_notax, io_id, io_type, io_price, ct_time, ct_ip, ct_send_cost, ct_direct, ct_select, ct_select_time )
|
||||
VALUES ";
|
||||
|
||||
for($k=0; $k<$opt_count; $k++) {
|
||||
$io_id = isset($_POST['io_id'][$it_id][$k]) ? preg_replace(G5_OPTION_ID_FILTER, '', $_POST['io_id'][$it_id][$k]) : '';
|
||||
$io_type = isset($_POST['io_type'][$it_id][$k]) ? preg_replace('#[^01]#', '', $_POST['io_type'][$it_id][$k]) : '';
|
||||
$io_id = $cart_validation['products'][$it_id]['rows'][$k]['io_id'];
|
||||
$io_type = $cart_validation['products'][$it_id]['rows'][$k]['io_type'];
|
||||
$io_value = isset($_POST['io_value'][$it_id][$k]) ? $_POST['io_value'][$it_id][$k] : '';
|
||||
|
||||
// 선택옵션정보가 존재하는데 선택된 옵션이 없으면 건너뜀
|
||||
if($lst_count && $io_id == '')
|
||||
continue;
|
||||
|
||||
// 구매할 수 없는 옵션은 건너뜀
|
||||
if($io_id && !$opt_list[$io_type][$io_id]['use'])
|
||||
continue;
|
||||
|
||||
$io_price = isset($opt_list[$io_type][$io_id]['price']) ? $opt_list[$io_type][$io_id]['price'] : 0;
|
||||
$io_price = $cart_validation['products'][$it_id]['rows'][$k]['io_price'];
|
||||
$ct_qty = isset($_POST['ct_qty'][$it_id][$k]) ? (int) $_POST['ct_qty'][$it_id][$k] : 0;
|
||||
|
||||
// 구매가격이 음수인지 체크
|
||||
@@ -317,14 +298,17 @@ else // 장바구니에 담기
|
||||
}
|
||||
|
||||
// 동일옵션의 상품이 있으면 수량 더함
|
||||
$sql2 = " select ct_id, io_type, ct_qty
|
||||
$sql2 = " select ct_id, io_type, ct_qty, ct_price, io_price
|
||||
from {$g5['g5_shop_cart_table']}
|
||||
where od_id = '$tmp_cart_id'
|
||||
and it_id = '$it_id'
|
||||
and io_id = '$io_id'
|
||||
and io_type = '$io_type'
|
||||
and ct_status = '쇼핑' ";
|
||||
$row2 = sql_fetch($sql2);
|
||||
if(isset($row2['ct_id']) && $row2['ct_id']) {
|
||||
if ((int) $row2['ct_price'] !== (int) $it['it_price'] || (int) $row2['io_price'] !== $io_price)
|
||||
alert('기존 장바구니의 상품 정보가 변경되었습니다. 삭제한 뒤 다시 담아 주십시오.');
|
||||
// 재고체크
|
||||
$tmp_ct_qty = $row2['ct_qty'];
|
||||
if(!$io_id)
|
||||
|
||||
@@ -24,6 +24,12 @@ else {
|
||||
if (get_cart_count($tmp_cart_id) == 0)
|
||||
alert('장바구니가 비어 있습니다.', G5_SHOP_URL.'/cart.php');
|
||||
|
||||
// KVE-2026-2345: 과거 장바구니의 옵션 종류와 가격도 주문 전에 다시 검증한다.
|
||||
$cart_validation_error = shop_validate_order_cart($tmp_cart_id);
|
||||
if ($cart_validation_error !== '') {
|
||||
alert($cart_validation_error, G5_SHOP_URL.'/cart.php');
|
||||
}
|
||||
|
||||
if (function_exists('before_check_cart_price')) {
|
||||
if(! before_check_cart_price($tmp_cart_id) ) alert('장바구니 금액에 변동사항이 있습니다.\n장바구니를 다시 확인해 주세요.', G5_SHOP_URL.'/cart.php');
|
||||
}
|
||||
|
||||
@@ -47,6 +47,12 @@ if (get_cart_count($tmp_cart_id) == 0) { // 장바구니에 담기
|
||||
alert('장바구니가 비어 있습니다.\\n\\n이미 주문하셨거나 장바구니에 담긴 상품이 없는 경우입니다.', G5_SHOP_URL.'/cart.php');
|
||||
}
|
||||
|
||||
// KVE-2026-2345: 과거 장바구니의 옵션 종류와 가격도 주문 전에 다시 검증한다.
|
||||
$cart_validation_error = shop_validate_order_cart($tmp_cart_id);
|
||||
if ($cart_validation_error !== '') {
|
||||
alert($cart_validation_error, G5_SHOP_URL.'/cart.php');
|
||||
}
|
||||
|
||||
// 변수 초기화
|
||||
$od_other_pay_type = '';
|
||||
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
# 장바구니 옵션 검증 테스트
|
||||
|
||||
KVE-2026-2345 수정의 회귀 검증이다. 운영 DB·PG를 사용하지 않는다.
|
||||
|
||||
## 공통 규칙
|
||||
|
||||
```bash
|
||||
php tests/shop_cart_validation.php
|
||||
```
|
||||
|
||||
DB 연결 없이 기본상품·선택옵션·추가옵션, 잘못된 종류·수량·재고, 정상 0원 상품, 과거 조작 행과 가격 변경을 검증한다.
|
||||
|
||||
## HTTP 및 MySQL
|
||||
|
||||
별도로 초기화한 테스트 전용 MySQL 서버의 Unix 소켓을 전달한다. PHP CLI의 `mysqli`, Python 3, `mysql` 클라이언트와 해당 테스트 서버의 비밀번호 없는 root 계정이 필요하다.
|
||||
|
||||
```bash
|
||||
python3 tests/shop_cart_http.py /tmp/kve2345-mysql.sock
|
||||
```
|
||||
|
||||
실행마다 고유한 `kve2345_test_*` DB를 생성하고 종료 시 삭제한다. 저장소 설치 SQL에서 상품·옵션·장바구니 테이블을 생성하며 MyISAM과 InnoDB에서 같은 검증을 수행한다. DB 엔진을 변환하는 대상은 생성된 테스트 테이블뿐이다.
|
||||
|
||||
공통 AJAX, 기본 테마 AJAX, 일반 장바구니 처리 파일을 임시 디렉터리에 복사하여 로컬 HTTP 서버로 실행한다. 인증·세션·리다이렉트 등 주변 기능은 테스트 부트스트랩으로 대체하고, SQL은 실제 MySQL에서 실행한다. 잘못된 요청이 DB를 바꾸지 않는지와 정상 추가·수량 병합·옵션별 합계를 확인한다.
|
||||
|
||||
PC·모바일 주문 처리 파일은 추가한 장바구니 검증 경계까지만 실행한다. 따라서 이 테스트는 실제 PG 승인·취소, 주문 저장 완료, 쿠폰·포인트·배송비 후속 정산, 실제 로그인·구매 권한, 브라우저 UI 또는 동시 요청 검증을 대신하지 않는다. 이 항목들은 배포 전 별도의 통합 환경에서 확인해야 한다.
|
||||
|
||||
## 구현 범위
|
||||
|
||||
두 AJAX 경로와 일반 추가 경로는 요청 전체의 상품·옵션 구성을 검증한 후 기존 변경 로직으로 진행한다. 옵션 종류·가격은 검증한 서버 데이터에서 가져온다. 주문 준비 및 PC·모바일 주문 제출은 선택된 기존 장바구니 행도 검증하여 과거 조작 행과 가격 불일치를 거부한다. 정상 무료상품은 허용한다.
|
||||
|
||||
기존 테이블 엔진은 변경하지 않으며, 장바구니·주문 전체의 트랜잭션이나 외부 PG 복구 흐름을 새로 도입하지 않는다. 따라서 사전 검증 이후의 DB 오류·동시 변경까지 모든 처리가 원자적이라고 보장하는 변경은 아니다.
|
||||
@@ -0,0 +1,181 @@
|
||||
#!/usr/bin/env python3
|
||||
"""격리 MySQL 전용: python3 tests/shop_cart_http.py /path/to/test-mysql.sock
|
||||
|
||||
고유 테스트 DB를 생성·삭제한다. 실제 장바구니 처리 파일을 HTTP로 실행하며,
|
||||
주문서는 PG/메일/주문 저장을 실행하기 전 검증 경계까지만 실행한다.
|
||||
인증·세션·메일·리다이렉트 등 주변 기능은 아래 테스트 부트스트랩으로 대체한다.
|
||||
"""
|
||||
import http.cookiejar
|
||||
import json
|
||||
from pathlib import Path
|
||||
import socket
|
||||
import subprocess
|
||||
import sys
|
||||
import tempfile
|
||||
import time
|
||||
import urllib.parse
|
||||
import urllib.request
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[1]
|
||||
SOCKET = sys.argv[1]
|
||||
DB = 'kve2345_test_' + str(time.time_ns())
|
||||
|
||||
def mysql(sql, database=True):
|
||||
command = ['mysql', '--no-defaults', '--socket=' + SOCKET, '-u', 'root', '-N', '-B']
|
||||
if database:
|
||||
command.append(DB)
|
||||
return subprocess.check_output(command, input="SET sql_mode='';" + sql, text=True).strip()
|
||||
|
||||
mysql('CREATE DATABASE `' + DB + '`', False)
|
||||
server = None
|
||||
try:
|
||||
import re
|
||||
schema = (ROOT / 'install/gnuboard5shop.sql').read_text()
|
||||
for table in ['item', 'item_option', 'cart']:
|
||||
statement = re.search(r'CREATE TABLE IF NOT EXISTS `g5_shop_' + table + r'` \(.*?;', schema, re.S).group()
|
||||
mysql("SET sql_mode='';" + statement)
|
||||
mysql("SET sql_mode=''; INSERT INTO g5_shop_item (it_id,it_name,it_use,it_price,it_stock_qty,it_sc_type) VALUES ('item','상품',1,50000,100,1),('other','다른 상품',1,30000,100,1)")
|
||||
with tempfile.TemporaryDirectory(prefix='kve2345-http-') as temp:
|
||||
directory = Path(temp)
|
||||
bootstrap = r'''<?php
|
||||
error_reporting(E_ALL);
|
||||
ini_set('display_errors', '0');
|
||||
session_start();
|
||||
define('_GNUBOARD_', true);
|
||||
define('G5_SHOP_URL', '/shop');
|
||||
define('G5_BBS_URL', '/bbs');
|
||||
define('G5_TIME_YMDHIS', date('Y-m-d H:i:s'));
|
||||
define('G5_OPTION_ID_FILTER', '/[\'"\\\\]/');
|
||||
define('G5_LIB_PATH', __DIR__);
|
||||
$g5 = array('g5_shop_item_table'=>'g5_shop_item', 'g5_shop_item_option_table'=>'g5_shop_item_option', 'g5_shop_cart_table'=>'g5_shop_cart');
|
||||
$member = array('mb_id'=>'', 'mb_level'=>1);
|
||||
$default = array('de_level_sell'=>1, 'de_pg_service'=>'kcp');
|
||||
$config = array('cf_use_point'=>0);
|
||||
$is_admin = true; $is_member = false;
|
||||
$sw_direct = !empty($_POST['sw_direct']) ? 1 : 0;
|
||||
$od_settle_case = '무통장';
|
||||
$link = new mysqli('localhost','root','',TEST_DB,0,TEST_SOCKET);
|
||||
$link->set_charset('utf8');
|
||||
$link->query("SET sql_mode=''");
|
||||
function sql_query($sql, $error=true) { global $link; return $link->query($sql); }
|
||||
function sql_fetch_array($result) { return $result->fetch_assoc(); }
|
||||
function sql_fetch($sql) { return sql_fetch_array(sql_query($sql)) ?: array(); }
|
||||
function sql_escape_string($s) { global $link; return $link->real_escape_string($s); }
|
||||
function sql_real_escape_string($s) { return sql_escape_string($s); }
|
||||
function safe_replace_regex($s,$type) { return preg_replace('/[^a-z0-9_\-]/i','',$s); }
|
||||
function clean_xss_tags($s) { return strip_tags($s); }
|
||||
function get_real_client_ip() { return '127.0.0.1'; }
|
||||
function set_cart_id($direct) { if(empty($_SESSION['ss_cart_id'])) $_SESSION['ss_cart_id'] = (string)random_int(100000000,999999999); $_SESSION['ss_cart_direct'] = $_SESSION['ss_cart_id']; }
|
||||
function get_session($key) { return isset($_SESSION[$key]) ? $_SESSION[$key] : ''; }
|
||||
function set_session($key,$value) { $_SESSION[$key]=$value; }
|
||||
function cart_item_clean() {}
|
||||
function is_inicis_order_pay($method) { return false; }
|
||||
function check_request_origin($url) {}
|
||||
function get_cart_count($id) { $r=sql_fetch("SELECT COUNT(*) n FROM g5_shop_cart WHERE od_id='".sql_escape_string($id)."'"); return $r['n']; }
|
||||
function get_it_stock_qty($id) { $r=sql_fetch("SELECT it_stock_qty FROM g5_shop_item WHERE it_id='".sql_escape_string($id)."'"); return $r['it_stock_qty']; }
|
||||
function get_option_stock_qty($id,$option,$type) { $r=sql_fetch("SELECT io_stock_qty FROM g5_shop_item_option WHERE it_id='".sql_escape_string($id)."' AND io_id='".sql_escape_string($option)."' AND io_type='".(int)$type."'"); return $r['io_stock_qty']; }
|
||||
function alert($message,$url='') { die(json_encode(array('error'=>$message))); }
|
||||
function goto_url($url) { die(json_encode(array('error'=>'','redirect'=>$url))); }
|
||||
require TEST_VALIDATOR;
|
||||
'''
|
||||
constants = '<?php\ndefine("TEST_DB",' + json.dumps(DB) + ');\ndefine("TEST_SOCKET",' + json.dumps(SOCKET) + ');\ndefine("TEST_VALIDATOR",' + json.dumps(str(ROOT / 'lib/shop.cartvalidate.lib.php')) + ');\n?>\n'
|
||||
(directory / 'bootstrap.php').write_text(constants + bootstrap)
|
||||
(directory / 'mailer.lib.php').write_text('<?php')
|
||||
routes = {'ajax': 'shop/ajax.action.php', 'theme': 'theme/basic/shop/ajax.action.php', 'normal': 'shop/cartupdate.php', 'pc_order': 'shop/orderformupdate.php', 'mobile_order': 'mobile/shop/orderformupdate.php'}
|
||||
for route, source in routes.items():
|
||||
text = (ROOT / source).read_text().replace("include_once('./_common.php');", "include_once('./bootstrap.php');", 1)
|
||||
if route.endswith('_order'):
|
||||
text = text.split('// 변수 초기화', 1)[0] + "echo json_encode(array('error'=>'','checkpoint'=>'validated')); exit;"
|
||||
(directory / (route + '.php')).write_text(text)
|
||||
with socket.socket() as sock:
|
||||
sock.bind(('127.0.0.1', 0))
|
||||
port = sock.getsockname()[1]
|
||||
with (directory / 'server.log').open('w+') as log:
|
||||
server = subprocess.Popen(['php', '-S', '127.0.0.1:' + str(port), '-t', temp], stdout=log, stderr=log, cwd=temp)
|
||||
for attempt in range(100):
|
||||
try:
|
||||
with socket.create_connection(('127.0.0.1', port), timeout=.1):
|
||||
break
|
||||
except OSError:
|
||||
time.sleep(.05)
|
||||
client = urllib.request.build_opener(urllib.request.HTTPCookieProcessor(http.cookiejar.CookieJar()))
|
||||
count = 0
|
||||
def request(route, fields):
|
||||
data = urllib.parse.urlencode(fields).encode()
|
||||
try:
|
||||
with client.open('http://127.0.0.1:' + str(port) + '/' + route + '.php', data) as response:
|
||||
return json.loads(response.read())
|
||||
except Exception:
|
||||
print((directory / 'server.log').read_text(), file=sys.stderr)
|
||||
raise
|
||||
def payload(types=('0',), ids=('',)):
|
||||
data = [('action','cart_update'),('it_id[0]','item')]
|
||||
for i, (kind, identity) in enumerate(zip(types, ids)):
|
||||
data += [('io_type[item]['+str(i)+']',kind),('io_id[item]['+str(i)+']',identity),('ct_qty[item]['+str(i)+']','1')]
|
||||
return data
|
||||
def expect(value, description):
|
||||
if not value:
|
||||
raise AssertionError(description)
|
||||
for engine in ['MyISAM','InnoDB']:
|
||||
for table in ['item','item_option','cart']:
|
||||
mysql('ALTER TABLE g5_shop_' + table + ' ENGINE=' + engine)
|
||||
for route in ['ajax','theme','normal']:
|
||||
mysql('DELETE FROM g5_shop_cart; DELETE FROM g5_shop_item_option')
|
||||
for bad in [payload(('1',)), payload(('0','1'),('','')), payload(('01',)), payload(('1x',)), payload(('0',),('missing',))]:
|
||||
result = request(route,bad)
|
||||
expect(result['error'] != '' and mysql('SELECT COUNT(*) FROM g5_shop_cart') == '0', route+' 잘못된 입력')
|
||||
count += 1
|
||||
malformed = [
|
||||
[('action','cart_update'),('it_id[0]','item'),('io_id[item][0]',''),('io_type[item][0][nested]','0'),('ct_qty[item][0]','1')],
|
||||
[('action','cart_update'),('it_id[0]','item'),('io_id[item][0][nested]',''),('io_type[item][0]','0'),('ct_qty[item][0]','1')],
|
||||
[('action','cart_update'),('it_id[0]','item'),('io_id[item][0]',''),('io_type[item][0]','0'),('ct_qty[item][1]','1')],
|
||||
[('action','cart_update'),('it_id[0]','item'),('io_id','bad'),('io_type[item][0]','0'),('ct_qty[item][0]','1')],
|
||||
payload()+[('ct_qty[item][0]','1.5')],
|
||||
payload()+[('io_value[item][0][nested]','bad')],
|
||||
]
|
||||
for bad in malformed:
|
||||
expect(request(route,bad)['error'] != '' and mysql('SELECT COUNT(*) FROM g5_shop_cart') == '0', '배열 구조 및 수량 검증')
|
||||
count += 1
|
||||
expect(request(route,payload())['error'] == '',route+' 정상 추가')
|
||||
expect(mysql('SELECT SUM(IF(io_type=1,io_price*ct_qty,(ct_price+io_price)*ct_qty)) FROM g5_shop_cart') == '50000',route+' 정상 가격')
|
||||
count += 1
|
||||
# 옵션 변경/바로구매 실패가 기존 행을 삭제하면 안 된다.
|
||||
before = mysql('SELECT ct_id,ct_qty FROM g5_shop_cart')
|
||||
expect(request(route,payload(('1',))+[('sw_direct','1'),('act','optionmod')])['error'] != '', '잘못된 수정 거부')
|
||||
expect(mysql('SELECT ct_id,ct_qty FROM g5_shop_cart') == before,'기존 장바구니 보존')
|
||||
count += 1
|
||||
bad_second = payload()+[('it_id[1]','other'),('io_type[other][0]','1'),('io_id[other][0]',''),('ct_qty[other][0]','1')]
|
||||
expect(request(route,bad_second)['error'] != '', '다중 상품 검증')
|
||||
expect(mysql('SELECT ct_id,ct_qty FROM g5_shop_cart') == before,'다중 요청 부분 변경 방지')
|
||||
count += 1
|
||||
expect(request(route,payload())['error'] == '', '정상 병합')
|
||||
expect(mysql('SELECT COUNT(*),SUM(ct_qty) FROM g5_shop_cart') == '1\t2','수량 병합 결과')
|
||||
count += 1
|
||||
# 패치 전 변조 행이 정상이 되어 보이도록 병합되지 않는지 확인
|
||||
mysql('UPDATE g5_shop_cart SET io_type=1,io_price=0,ct_select=1')
|
||||
for order_route in ['pc_order','mobile_order']:
|
||||
expect(request(order_route,[])['error'] != '', '과거 조작 주문 거부')
|
||||
count += 1
|
||||
mysql('UPDATE g5_shop_cart SET io_type=0')
|
||||
for order_route in ['pc_order','mobile_order']:
|
||||
expect(request(order_route,[]) .get('checkpoint') == 'validated','정상 주문 검증 진입')
|
||||
count += 1
|
||||
mysql("DELETE FROM g5_shop_cart; INSERT INTO g5_shop_item_option (it_id,io_id,io_type,io_use,io_price,io_stock_qty) VALUES ('item','wrap',1,1,1000,100)")
|
||||
expect(request(route,payload(('0','1'),('','wrap')))['error'] == '', '정상 보조옵션')
|
||||
expect(mysql('SELECT SUM(IF(io_type=1,io_price*ct_qty,(ct_price+io_price)*ct_qty)) FROM g5_shop_cart') == '51000','보조옵션 포함 합계')
|
||||
count += 1
|
||||
# 타입은 다르지만 ID가 같은 옵션을 서로 병합하지 않는다.
|
||||
mysql("DELETE FROM g5_shop_cart; DELETE FROM g5_shop_item_option; INSERT INTO g5_shop_item_option (it_id,io_id,io_type,io_use,io_price,io_stock_qty) VALUES ('item','same',0,1,2000,100),('item','same',1,1,1000,100)")
|
||||
pair = payload(('0','1'),('same','same'))
|
||||
expect(request(route,pair)['error'] == '', '동일 ID 다른 종류 추가')
|
||||
expect(request(route,pair)['error'] == '', '동일 ID 다른 종류 병합')
|
||||
expect(mysql('SELECT COUNT(*),SUM(IF(io_type=1,io_price*ct_qty,(ct_price+io_price)*ct_qty)) FROM g5_shop_cart') == '2\t106000', '서로 다른 종류 병합 방지')
|
||||
count += 1
|
||||
server.terminate(); server.wait(); server = None
|
||||
log.seek(0); errors = log.read()
|
||||
expect('PHP Warning' not in errors and 'PHP Fatal' not in errors,errors)
|
||||
print(str(count)+'개 HTTP/DB 검증 통과 (MyISAM·InnoDB, 주문은 검증 경계까지)')
|
||||
finally:
|
||||
if server:
|
||||
server.terminate(); server.wait()
|
||||
mysql('DROP DATABASE `' + DB + '`', False)
|
||||
@@ -0,0 +1,75 @@
|
||||
<?php
|
||||
// 실행: php tests/shop_cart_validation.php (DB 연결 없이 공통 규칙 검증)
|
||||
if (PHP_SAPI !== 'cli') exit;
|
||||
define('_GNUBOARD_', true);
|
||||
require dirname(__FILE__).'/../lib/shop.cartvalidate.lib.php';
|
||||
$checks = 0;
|
||||
function check_cart($name, $item, $options, $rows, $expected, $stored = false)
|
||||
{
|
||||
global $checks;
|
||||
$result = shop_validate_cart_rows($item, $options, $rows, $stored);
|
||||
$actual = $result['error'] === '' ? $result['total'] : false;
|
||||
if ($actual !== $expected) {
|
||||
fwrite(STDERR, $name.': '.json_encode($result)."\n");
|
||||
exit(1);
|
||||
}
|
||||
$checks++;
|
||||
}
|
||||
function test_row($id = '', $type = '0', $qty = '1')
|
||||
{
|
||||
return array('io_id' => $id, 'io_type' => $type, 'ct_qty' => $qty);
|
||||
}
|
||||
$item = array('it_id' => 'item', 'it_use' => 1, 'it_tel_inq' => 0, 'it_price' => 50000, 'it_stock_qty' => 100, 'it_buy_min_qty' => 0, 'it_buy_max_qty' => 0);
|
||||
$base = test_row();
|
||||
$addon = array('it_id' => 'item', 'io_id' => 'wrap', 'io_type' => '1', 'io_use' => 1, 'io_price' => 1000, 'io_stock_qty' => 100);
|
||||
$selection = array('it_id' => 'item', 'io_id' => 'red', 'io_type' => '0', 'io_use' => 1, 'io_price' => 2000, 'io_stock_qty' => 100);
|
||||
check_cart('기본상품', $item, array(), array($base), 50000);
|
||||
check_cart('빈 보조옵션', $item, array(), array(test_row('', '1')), false);
|
||||
check_cart('정상 행 뒤 빈 보조옵션', $item, array(), array($base, test_row('', '1')), false);
|
||||
foreach (array('', '01', '11', '2', '-1', '1x', '1.0', true, array('1')) as $type)
|
||||
check_cart('종류 형식', $item, array(), array(test_row('', $type)), false);
|
||||
foreach (array('0', '-1', '1.5', '1x', '2147483648', array(1), true) as $qty)
|
||||
check_cart('수량 형식', $item, array(), array(test_row('', '0', $qty)), false);
|
||||
check_cart('수량 재고 초과', $item, array(), array(test_row('', '0', '101')), false);
|
||||
check_cart('동일 옵션 누적 재고', $item, array(), array(test_row('', '0', '60'), test_row('', '0', '60')), false);
|
||||
check_cart('정상 추가옵션', $item, array($addon), array($base, test_row('wrap', '1')), 51000);
|
||||
check_cart('첫 행 추가옵션', $item, array($addon), array(test_row('wrap', '1'), $base), false);
|
||||
check_cart('추가옵션 단독', $item, array($addon), array(test_row('wrap', '1')), false);
|
||||
check_cart('추가옵션 종류 불일치', $item, array($addon), array(test_row('wrap', '0')), false);
|
||||
check_cart('없는 옵션', $item, array(), array($base, test_row('wrap', '1')), false);
|
||||
$disabled = $addon; $disabled['io_use'] = 0;
|
||||
check_cart('비활성 옵션', $item, array($disabled), array($base, test_row('wrap', '1')), false);
|
||||
$foreign = $addon; $foreign['it_id'] = 'other';
|
||||
check_cart('타 상품 옵션', $item, array($foreign), array($base, test_row('wrap', '1')), false);
|
||||
check_cart('모호한 중복 옵션', $item, array($addon, $addon), array($base, test_row('wrap', '1')), false);
|
||||
check_cart('필수 선택옵션 누락', $item, array($selection), array($base), false);
|
||||
check_cart('정상 선택옵션', $item, array($selection), array(test_row('red')), 52000);
|
||||
$disabled = $selection; $disabled['io_use'] = 0;
|
||||
check_cart('모든 선택옵션 비활성', $item, array($disabled), array($base), false);
|
||||
$same_id = $addon; $same_id['io_id'] = 'red';
|
||||
check_cart('다른 종류의 동일 ID', $item, array($selection, $same_id), array(test_row('red'), test_row('red', '1')), 53000);
|
||||
$free = $item; $free['it_price'] = 0;
|
||||
check_cart('정상 무료상품', $free, array(), array($base), 0);
|
||||
$discount = $selection; $discount['io_price'] = -50000;
|
||||
check_cart('정상 0원 선택옵션', $item, array($discount), array(test_row('red')), 0);
|
||||
$discount['io_price'] = -50001;
|
||||
check_cart('음수 합계', $item, array($discount), array(test_row('red')), false);
|
||||
$negative = $addon; $negative['io_price'] = -1;
|
||||
check_cart('음수 추가옵션', $item, array($negative), array($base, test_row('wrap', '1')), false);
|
||||
$stored = $base; $stored['ct_price'] = '50000'; $stored['io_price'] = '0';
|
||||
check_cart('정상 저장 행', $item, array(), array($stored), 50000, true);
|
||||
$stored['io_type'] = '1';
|
||||
check_cart('과거 조작 행', $item, array(), array($stored), false, true);
|
||||
$stored['io_type'] = '0'; $stored['ct_price'] = 1;
|
||||
check_cart('상품가격 변경', $item, array(), array($stored), false, true);
|
||||
$stored['ct_price'] = 50000; $stored['io_price'] = 1;
|
||||
check_cart('옵션가격 불일치', $item, array(), array($stored), false, true);
|
||||
foreach (array('it_use' => 0, 'it_tel_inq' => 1, 'it_id' => '') as $key => $value) {
|
||||
$invalid = $item; $invalid[$key] = $value;
|
||||
check_cart('구매불가 상품', $invalid, array(), array($base), false);
|
||||
}
|
||||
$limited = $item; $limited['it_buy_min_qty'] = 2;
|
||||
check_cart('최소 수량', $limited, array(), array($base), false);
|
||||
$limited = $item; $limited['it_buy_max_qty'] = 1;
|
||||
check_cart('최대 수량', $limited, array(), array(test_row('', '0', '2')), false);
|
||||
echo $checks."개 검증 통과\n";
|
||||
@@ -78,35 +78,20 @@ switch ($action) {
|
||||
if ($count < 1)
|
||||
die(json_encode(array('error' => '장바구니에 담을 상품을 선택하여 주십시오.')));
|
||||
|
||||
$cart_validation = shop_validate_cart_request($_POST);
|
||||
if ($cart_validation['error'] !== '')
|
||||
die(json_encode(array('error' => $cart_validation['error'])));
|
||||
|
||||
$ct_count = 0;
|
||||
for($i=0; $i<$count; $i++) {
|
||||
$it_id = isset($_POST['it_id'][$i]) ? safe_replace_regex($_POST['it_id'][$i], 'it_id') : '';
|
||||
$opt_count = (isset($_POST['io_id'][$it_id]) && is_array($_POST['io_id'][$it_id])) ? count($_POST['io_id'][$it_id]) : 0;
|
||||
|
||||
// 상품정보
|
||||
$it = get_shop_item($it_id, false);
|
||||
$it = $cart_validation['products'][$it_id]['item'];
|
||||
if(!$it['it_id'])
|
||||
die(json_encode(array('error' => '상품정보가 존재하지 않습니다.')));
|
||||
|
||||
// 옵션정보를 얻어서 배열에 저장
|
||||
$opt_list = array();
|
||||
$sql = " select * from {$g5['g5_shop_item_option_table']} where it_id = '$it_id' order by io_no asc ";
|
||||
$result = sql_query($sql);
|
||||
$lst_count = 0;
|
||||
for($k=0; $row=sql_fetch_array($result); $k++) {
|
||||
$opt_list[$row['io_type']][$row['io_id']]['id'] = $row['io_id'];
|
||||
$opt_list[$row['io_type']][$row['io_id']]['use'] = $row['io_use'];
|
||||
$opt_list[$row['io_type']][$row['io_id']]['price'] = $row['io_price'];
|
||||
$opt_list[$row['io_type']][$row['io_id']]['stock'] = $row['io_stock_qty'];
|
||||
|
||||
// 선택옵션 개수
|
||||
if(!$row['io_type'])
|
||||
$lst_count++;
|
||||
}
|
||||
|
||||
if($lst_count > 0 && !trim($_POST['io_id'][$it_id][$i]) && $_POST['io_type'][$it_id][$i] == 0)
|
||||
die(json_encode(array('error' => '상품의 선택옵션을 선택해 주십시오.')));
|
||||
|
||||
for($k=0; $k<$opt_count; $k++) {
|
||||
$post_ct_qty = isset($_POST['ct_qty'][$it_id][$k]) ? (int) $_POST['ct_qty'][$it_id][$k] : 0;
|
||||
if ($post_ct_qty < 1)
|
||||
@@ -155,25 +140,17 @@ switch ($action) {
|
||||
|
||||
// 장바구니에 Insert
|
||||
$comma = '';
|
||||
$ct_count = 0;
|
||||
$sql = " INSERT INTO {$g5['g5_shop_cart_table']}
|
||||
( od_id, mb_id, it_id, it_name, it_sc_type, it_sc_method, it_sc_price, it_sc_minimum, it_sc_qty, ct_status, ct_price, ct_point, ct_point_use, ct_stock_use, ct_option, ct_qty, ct_notax, io_id, io_type, io_price, ct_time, ct_ip, ct_send_cost, ct_direct, ct_select, ct_select_time )
|
||||
VALUES ";
|
||||
|
||||
for($k=0; $k<$opt_count; $k++) {
|
||||
$io_id = isset($_POST['io_id'][$it_id][$k]) ? preg_replace(G5_OPTION_ID_FILTER, '', $_POST['io_id'][$it_id][$k]) : '';
|
||||
$io_type = isset($_POST['io_type'][$it_id][$k]) ? preg_replace('#[^01]#', '', $_POST['io_type'][$it_id][$k]) : '';
|
||||
$io_id = $cart_validation['products'][$it_id]['rows'][$k]['io_id'];
|
||||
$io_type = $cart_validation['products'][$it_id]['rows'][$k]['io_type'];
|
||||
$io_value = isset($_POST['io_value'][$it_id][$k]) ? $_POST['io_value'][$it_id][$k] : '';
|
||||
|
||||
// 선택옵션정보가 존재하는데 선택된 옵션이 없으면 건너뜀
|
||||
if($lst_count && $io_id == '')
|
||||
continue;
|
||||
|
||||
$opt_list_type_id_use = isset($opt_list[$io_type][$io_id]['use']) ? $opt_list[$io_type][$io_id]['use'] : '';
|
||||
// 구매할 수 없는 옵션은 건너뜀
|
||||
if($io_id && ! $opt_list_type_id_use)
|
||||
continue;
|
||||
|
||||
$io_price = isset($opt_list[$io_type][$io_id]['price']) ? $opt_list[$io_type][$io_id]['price'] : 0;
|
||||
$io_price = $cart_validation['products'][$it_id]['rows'][$k]['io_price'];
|
||||
$ct_qty = isset($_POST['ct_qty'][$it_id][$k]) ? (int) $_POST['ct_qty'][$it_id][$k] : 0;
|
||||
|
||||
// 구매가격이 음수인지 체크
|
||||
@@ -186,14 +163,17 @@ switch ($action) {
|
||||
}
|
||||
|
||||
// 동일옵션의 상품이 있으면 수량 더함
|
||||
$sql2 = " select ct_id, io_type, ct_qty
|
||||
$sql2 = " select ct_id, io_type, ct_qty, ct_price, io_price
|
||||
from {$g5['g5_shop_cart_table']}
|
||||
where od_id = '$tmp_cart_id'
|
||||
and it_id = '$it_id'
|
||||
and io_id = '$io_id'
|
||||
and io_type = '$io_type'
|
||||
and ct_status = '쇼핑' ";
|
||||
$row2 = sql_fetch($sql2);
|
||||
if(isset($row2['ct_id']) && $row2['ct_id']) {
|
||||
if ((int) $row2['ct_price'] !== (int) $it['it_price'] || (int) $row2['io_price'] !== $io_price)
|
||||
die(json_encode(array('error' => '기존 장바구니의 상품 정보가 변경되었습니다. 삭제한 뒤 다시 담아 주십시오.')));
|
||||
// 재고체크
|
||||
$tmp_ct_qty = $row2['ct_qty'];
|
||||
if(!$io_id)
|
||||
|
||||
Reference in New Issue
Block a user