Ver3.0 ๐ ๊ณ ๋๋ชฐ ์ํ ๋ฌถ์ ๋ฐฐ์ก ๋ก์ง ๊ตฌํ: ๋ฐฐ์ก๋น ์ต์ ํ์ ๋ชจ๋ ๊ฒ

๐ ๊ณ ๋๋ชฐ ์ํ ๋ฌถ์ ๋ฐฐ์ก ๋ก์ง ๊ตฌํ: ๋ฐฐ์ก๋น ์ต์ ํ์ ๋ชจ๋ ๊ฒ
์ผํ๋ชฐ ๊ฐ๋ฐ์๋ฅผ ์ํ ์ค์ ๋ฐฐ์ก ๋ก์ง ์๋ฒฝ ๊ฐ์ด๋
๐ฆ ์ ๋ฌถ์ ๋ฐฐ์ก ๋ก์ง์ด ์ค์ํ ๊น?
์๋ ! ์ค๋์ ๊ณ ๋๋ชฐ์์ ์ํ ๋ฌถ์ ๋ฐฐ์ก ๋ก์ง์ ๊ตฌํํ๋ ๋ฐฉ๋ฒ์ ๋ํด ์ด์ผ๊ธฐํด๋ณผ๊ฒ. ์ผํ๋ชฐ์ ์ด์ํ๋ค ๋ณด๋ฉด ๊ณ ๊ฐ๋ค์ด ์ฌ๋ฌ ์ํ์ ํ ๋ฒ์ ์ฃผ๋ฌธํ๋ ๊ฒฝ์ฐ๊ฐ ์ ๋ง ๋ง์์? ์ด๋ ๋ฐฐ์ก๋น๋ฅผ ์ด๋ป๊ฒ ๊ณ์ฐํ ์ง๊ฐ ์ ๋ง ์ค์ํ ๋ฌธ์ ์ผ. ๐ค
์๋ฅผ ๋ค์ด๋ณผ๊ฒ. ๊ณ ๊ฐ์ด A์ํ(๋ฐฐ์ก๋น 3,000์)๊ณผ B์ํ(๋ฐฐ์ก๋น 3,000์)์ ํจ๊ป ์ฃผ๋ฌธํ๋ค๊ณ ๊ฐ์ ํด๋ณด์. ์ด๋ ๋ฐฐ์ก๋น๋ฅผ 6,000์์ผ๋ก ๋ฐ์ ๊ฑด๊ฐ, ์๋๋ฉด 3,000์๋ง ๋ฐ์ ๊ฑด๊ฐ? ๊ฐ์ ๋ฐฐ์ก์ง๋ก ๋ณด๋ด๋๋ฐ ๋ ๋ฒ ๋ฐ๋ ๊ฑด ๊ณ ๊ฐ ์ ์ฅ์์ ์ต์ธํ๊ฒ ์ง? ๋ฐ๋ก ์ด๋ฐ ๋ฌธ์ ๋ฅผ ํด๊ฒฐํ๋ ๊ฒ ๋ฌถ์ ๋ฐฐ์ก ๋ก์ง์ด์ผ.
โข ๊ตฌ๋งค ์ ํ์จ ์ฆ๊ฐ: ์ฅ๋ฐ๊ตฌ๋ ์ดํ๋ฅ ๊ฐ์
โข ์ด์ ํจ์จ์ฑ: ์๋ํ๋ ๋ฐฐ์ก๋น ๊ณ์ฐ
โข ๋ฌผ๋ฅ ์ต์ ํ: ๋์ผ ๋ฐฐ์ก์ง ์ํ ํตํฉ ์ฒ๋ฆฌ
์ค์ ๋ก ์ฌ๋ฅ๋ท์์๋ ๋ค์ํ ์ผํ๋ชฐ ๊ฐ๋ฐ ๊ด๋ จ ์ฌ๋ฅ์ ๊ฑฐ๋ํ๋๋ฐ, ๋ฐฐ์ก ๋ก์ง ๊ตฌํ์ ํญ์ ์์๊ถ์ ๋ญํฌ๋๋ ์์ฒญ์ฌํญ์ด์ผ. ๊ทธ๋งํผ ์ค์ํ๊ณ ๊น๋ค๋ก์ด ๋ถ๋ถ์ด๋ผ๋ ๊ฑฐ์ง! ๐
๐ฏ ๊ณ ๋๋ชฐ์ ๋ฐฐ์ก๋น ๊ณ์ฐ ๋ฐฉ์ ์ดํดํ๊ธฐ
๋ณธ๊ฒฉ์ ์ผ๋ก ๋ก์ง ๊ตฌํ์ ๋ค์ด๊ฐ๊ธฐ ์ ์, ๊ณ ๋๋ชฐ์ ๊ธฐ๋ณธ ๋ฐฐ์ก๋น ๊ณ์ฐ ๋ฐฉ์์ ์ดํดํด์ผ ํด. ๊ณ ๋๋ชฐ์ ๋ค์ํ ๋ฐฐ์ก๋น ์ ์ฑ ์ ์ง์ํ๋๋ฐ, ํฌ๊ฒ ๋ค ๊ฐ์ง ๋ฐฉ์์ผ๋ก ๋๋ ์ ์์ด.
| ๋ฐฐ์ก๋น ๋ฐฉ์ | ์ค๋ช | ์ ์ฉ ์ฌ๋ก |
|---|---|---|
| ๊ณ ์ ๋ฐฐ์ก๋น | ์ํ์ ๊ด๊ณ์์ด ์ผ์ ๊ธ์ก ๋ถ๊ณผ | ์ผ๋ฐ ์๋ฅ, ์ํ ์กํ |
| ์กฐ๊ฑด๋ถ ๋ฌด๋ฃ๋ฐฐ์ก | ํน์ ๊ธ์ก ์ด์ ๊ตฌ๋งค ์ ๋ฌด๋ฃ | 3๋ง์ ์ด์ ๋ฌด๋ฃ๋ฐฐ์ก |
| ์๋๋ณ ๋ฐฐ์ก๋น | ๊ตฌ๋งค ์๋์ ๋ฐ๋ผ ๋ฐฐ์ก๋น ์ฐจ๋ฑ | ๋ํ ๊ฐ์ , ๊ฐ๊ตฌ |
| ์ง์ญ๋ณ ์ฐจ๋ฑ ๋ฐฐ์ก๋น | ๋ฐฐ์ก ์ง์ญ์ ๋ฐ๋ผ ์ถ๊ฐ ๋น์ฉ | ์ ์ฃผ/๋์์ฐ๊ฐ ์ถ๊ฐ ๋ฐฐ์ก๋น |
์ด ์ค์์ ๋ฌถ์ ๋ฐฐ์ก ๋ก์ง์ ์ฃผ๋ก ๊ณ ์ ๋ฐฐ์ก๋น์ ์กฐ๊ฑด๋ถ ๋ฌด๋ฃ๋ฐฐ์ก ๋ฐฉ์์์ ๊ฐ์ฅ ๋ง์ด ํ์ฉ๋ผ. ์๋ํ๋ฉด ์ฌ๋ฌ ์ํ์ ๋ฌถ์ด์ ๋ฐฐ์ก๋น๋ฅผ ๊ณ์ฐํ ๋ ๊ธฐ์ค์ด ๋ช ํํด์ผ ํ๊ฑฐ๋ ! ๐ช
์ฌ๊ธฐ์ ๊ฐ ๋ฐฐ์ก ์ ์ฑ ๋ณ๋ก ๋ฌถ์ ๋ฐฐ์ก ์ฌ๋ถ๋ฅผ ์ค์ ํ ์ ์์ด. ๊ธฐ๋ณธ์ ์ผ๋ก '๋ฐฐ์ก๋น ํฉ์ฐ' ๋๋ '๋ฐฐ์ก๋น ์ ํ' ์ต์ ์ ์ ๊ณตํ์ง.
โ๏ธ ๋ฌถ์ ๋ฐฐ์ก ๋ก์ง์ ํต์ฌ ์๊ณ ๋ฆฌ์ฆ
์, ์ด์ ๋ณธ๊ฒฉ์ ์ผ๋ก ๋ก์ง์ ๊ตฌํํด๋ณผ ์๊ฐ์ด์ผ! ๋ฌถ์ ๋ฐฐ์ก ๋ก์ง์ ํต์ฌ์ ๊ทธ๋ฃนํ(Grouping)๊ณผ ์ฐ์ ์์ ๊ฒฐ์ (Priority)์ด์ผ. ์ฝ๊ฒ ๋งํ๋ฉด, ์ด๋ค ์ํ๋ค์ ๋ฌถ์ ์ ์๋์ง ํ๋จํ๊ณ , ๊ทธ์ค์์ ์ด๋ค ๋ฐฐ์ก๋น๋ฅผ ์ ์ฉํ ์ง ๊ฒฐ์ ํ๋ ๊ฑฐ์ง.
๐ 1๋จ๊ณ: ๋ฐฐ์ก ๊ทธ๋ฃน ๋ถ๋ฅ
๋จผ์ ์ฅ๋ฐ๊ตฌ๋์ ๋ด๊ธด ์ํ๋ค์ ๋ฐฐ์ก ๊ทธ๋ฃน๋ณ๋ก ๋ถ๋ฅํด์ผ ํด. ๊ฐ์ ๊ทธ๋ฃน์ ์ํ ์ํ๋ค๋ง ๋ฌถ์ ๋ฐฐ์ก์ด ๊ฐ๋ฅํ๊ฑฐ๋ !
๊ฐ ์ํ์ ๋ฐฐ์ก ์ ์ฑ ID๋ฅผ ๊ฐ์ง๊ณ ์์ด. ์ด ID๊ฐ ๊ฐ์ ์ํ๋ค๋ผ๋ฆฌ ๋ฌถ์ ์ ์์ด. ์๋ฅผ ๋ค์ด '์ผ๋ฐ๋ฐฐ์ก' ์ ์ฑ (ID: 1)์ ์ฌ์ฉํ๋ ์ํ๋ค์ ํ๋์ ๊ทธ๋ฃน์ด ๋๋ ๊ฑฐ์ง.
๊ฐ์ ๋ฐฐ์ก ์ ์ฑ ์ด๋ผ๋ ์ถ๋ฐ์ง๊ฐ ๋ค๋ฅด๋ฉด ๋ณ๋ ๊ทธ๋ฃน์ผ๋ก ๋ถ๋ฆฌํด์ผ ํด. A์ฐฝ๊ณ ์์ ์ถ๋ฐํ๋ ์ํ๊ณผ B์ฐฝ๊ณ ์์ ์ถ๋ฐํ๋ ์ํ์ ๋ฐ๋ก ๋ฐฐ์ก๋๋๊น!
์ผ๋ถ ์ํ์ ๋ค๋ฅธ ์ํ๊ณผ ํจ๊ป ๋ฐฐ์กํ ์ ์์ด. ์๋ฅผ ๋ค์ด ๋์ฅ/๋๋ ์ํ์ ์ผ๋ฐ ์ํ๊ณผ ๋ฌถ์ ์ ์์ง. ์ด๋ฐ ์์ธ ์ผ์ด์ค๋ฅผ ์ฒดํฌํด์ผ ํด.
๐ 2๋จ๊ณ: ๋ฐฐ์ก๋น ๊ณ์ฐ ๋ก์ง
๊ทธ๋ฃน์ด ๋๋์์ผ๋ฉด ์ด์ ๊ฐ ๊ทธ๋ฃน์ ๋ฐฐ์ก๋น๋ฅผ ๊ณ์ฐํ ์ฐจ๋ก์ผ. ์ฌ๊ธฐ์ ์ฌ๋ฌ ๊ฐ์ง ๋ฐฉ์์ ์ ์ฉํ ์ ์์ด.
// ๊ธฐ๋ณธ ๋ฐฐ์ก๋น ๊ณ์ฐ ๋ก์ง ์์ (PHP)
function calculateShippingFee($cartItems) {
$shippingGroups = [];
// 1๋จ๊ณ: ๋ฐฐ์ก ๊ทธ๋ฃน ๋ถ๋ฅ
foreach ($cartItems as $item) {
$groupKey = $item['delivery_sno'] . '_' . $item['warehouse_code'];
if (!isset($shippingGroups[$groupKey])) {
$shippingGroups[$groupKey] = [
'items' => [],
'total_price' => 0,
'base_fee' => $item['delivery_fee'],
'free_condition' => $item['free_delivery_price']
];
}
$shippingGroups[$groupKey]['items'][] = $item;
$shippingGroups[$groupKey]['total_price'] += $item['price'] * $item['quantity'];
}
// 2๋จ๊ณ: ๊ฐ ๊ทธ๋ฃน๋ณ ๋ฐฐ์ก๋น ๊ณ์ฐ
$totalShippingFee = 0;
foreach ($shippingGroups as $group) {
// ๋ฌด๋ฃ๋ฐฐ์ก ์กฐ๊ฑด ํ์ธ
if ($group['total_price'] >= $group['free_condition']) {
continue; // ๋ฌด๋ฃ๋ฐฐ์ก
}
// ๋ฐฐ์ก๋น ํฉ์ฐ ๋ฐฉ์์ ๋ฐ๋ผ ์ฒ๋ฆฌ
$totalShippingFee += $group['base_fee'];
}
return $totalShippingFee;
}
๐ ๏ธ ๊ณ ๋๋ชฐ ์ค์ ๊ตฌํ ๊ฐ์ด๋
์ด๋ก ์ ์ถฉ๋ถํ ์์๋ดค์ผ๋, ์ด์ ๊ณ ๋๋ชฐ์์ ์ค์ ๋ก ์ด๋ป๊ฒ ๊ตฌํํ๋์ง ๋จ๊ณ๋ณ๋ก ์ดํด๋ณผ๊ฒ. ๊ณ ๋๋ชฐ์ ์์ฒด ํ๋ ์์ํฌ๋ฅผ ์ฌ์ฉํ๊ธฐ ๋๋ฌธ์ ํน์ ํ์ผ๋ค์ ์์ ํด์ผ ํด. ๐
๐ง Step 1: ๋ฐฐ์ก๋น ๊ณ์ฐ ๋ชจ๋ ์์
๊ณ ๋๋ชฐ์ ๋ฐฐ์ก๋น ๊ณ์ฐ์ ์ฃผ๋ก module/Order/OrderCalculation.php ํ์ผ์์ ์ฒ๋ฆฌ๋ผ. ์ด ํ์ผ์ ์ด์ด์ ๋ฐฐ์ก๋น ๊ณ์ฐ ๋ฉ์๋๋ฅผ ์ฐพ์์ผ ํด.
// module/Order/OrderCalculation.php ๋ด๋ถ
class OrderCalculation {
/**
* ๋ฌถ์ ๋ฐฐ์ก๋น ๊ณ์ฐ
* @param array $cartData ์ฅ๋ฐ๊ตฌ๋ ๋ฐ์ดํฐ
* @return array ๋ฐฐ์ก๋น ์ ๋ณด
*/
public function getDeliveryGroupCharge($cartData) {
$deliveryGroup = [];
$deliveryCharge = [];
// ๋ฐฐ์ก๋น ๊ทธ๋ฃน ์์ฑ
foreach ($cartData as $key => $val) {
// ๋ฐฐ์ก ์ ์ฑ
๋ณ ๊ทธ๋ฃนํ
$groupKey = $val['deliverySno'] . '_' . $val['scmNo'];
if (!isset($deliveryGroup[$groupKey])) {
$deliveryGroup[$groupKey] = [
'deliverySno' => $val['deliverySno'],
'scmNo' => $val['scmNo'],
'goodsPrice' => 0,
'deliveryFee' => $val['deliveryFee'],
'deliveryFreePrice' => $val['deliveryFreePrice'],
'deliveryMethod' => $val['deliveryMethod'],
'goods' => []
];
}
// ์ํ ์ ๋ณด ์ถ๊ฐ
$deliveryGroup[$groupKey]['goods'][] = $val;
$deliveryGroup[$groupKey]['goodsPrice'] += $val['price'] * $val['goodsCnt'];
}
// ๊ฐ ๊ทธ๋ฃน๋ณ ๋ฐฐ์ก๋น ๊ณ์ฐ
foreach ($deliveryGroup as $groupKey => $group) {
$fee = 0;
// ๋ฐฐ์ก๋น ๊ณ์ฐ ๋ฐฉ์์ ๋ฐ๋ผ ๋ถ๊ธฐ
switch ($group['deliveryMethod']) {
case 'fixed': // ๊ณ ์ ๋ฐฐ์ก๋น
if ($group['goodsPrice'] < $group['deliveryFreePrice']) {
$fee = $group['deliveryFee'];
}
break;
case 'quantity': // ์๋๋ณ ๋ฐฐ์ก๋น
$totalQty = 0;
foreach ($group['goods'] as $goods) {
$totalQty += $goods['goodsCnt'];
}
$fee = $this->calculateQuantityBasedFee($totalQty, $group);
break;
case 'weight': // ๋ฌด๊ฒ๋ณ ๋ฐฐ์ก๋น
$totalWeight = 0;
foreach ($group['goods'] as $goods) {
$totalWeight += $goods['weight'] * $goods['goodsCnt'];
}
$fee = $this->calculateWeightBasedFee($totalWeight, $group);
break;
}
// ์ง์ญ๋ณ ์ถ๊ฐ ๋ฐฐ์ก๋น ๊ณ์ฐ
$fee += $this->getRegionalExtraFee($group);
$deliveryCharge[$groupKey] = [
'fee' => $fee,
'group' => $group
];
}
return $deliveryCharge;
}
/**
* ์๋๋ณ ๋ฐฐ์ก๋น ๊ณ์ฐ
*/
private function calculateQuantityBasedFee($quantity, $group) {
// ์๋ ๊ตฌ๊ฐ๋ณ ๋ฐฐ์ก๋น ์ค์ ์กฐํ
$feeConfig = $this->getQuantityFeeConfig($group['deliverySno']);
foreach ($feeConfig as $config) {
if ($quantity >= $config['minQty'] && $quantity <= $config['maxQty']) {
return $config['fee'];
}
}
return $group['deliveryFee']; // ๊ธฐ๋ณธ ๋ฐฐ์ก๋น
}
/**
* ๋ฌด๊ฒ๋ณ ๋ฐฐ์ก๋น ๊ณ์ฐ
*/
private function calculateWeightBasedFee($weight, $group) {
// ๋ฌด๊ฒ ๊ตฌ๊ฐ๋ณ ๋ฐฐ์ก๋น ์ค์ ์กฐํ
$feeConfig = $this->getWeightFeeConfig($group['deliverySno']);
foreach ($feeConfig as $config) {
if ($weight >= $config['minWeight'] && $weight <= $config['maxWeight']) {
return $config['fee'];
}
}
return $group['deliveryFee']; // ๊ธฐ๋ณธ ๋ฐฐ์ก๋น
}
/**
* ์ง์ญ๋ณ ์ถ๊ฐ ๋ฐฐ์ก๋น
*/
private function getRegionalExtraFee($group) {
// ๋ฐฐ์ก์ง ์ฃผ์ ์ ๋ณด ๊ฐ์ ธ์ค๊ธฐ
$address = $this->getDeliveryAddress();
// ์ ์ฃผ/๋์์ฐ๊ฐ ์ฒดํฌ
if ($this->isRemoteArea($address)) {
return $group['remoteAreaFee'] ?? 3000; // ๊ธฐ๋ณธ 3,000์
}
return 0;
}
}
๐ง Step 2: ์ฅ๋ฐ๊ตฌ๋ ํ์ด์ง ์ฐ๋
๋ฐฐ์ก๋น ๊ณ์ฐ ๋ก์ง์ ๋ง๋ค์์ผ๋ฉด, ์ด์ ์ฅ๋ฐ๊ตฌ๋ ํ์ด์ง์์ ์ด๋ฅผ ํธ์ถํด์ผ ํด. ๊ณ ๋๋ชฐ์ ์ฅ๋ฐ๊ตฌ๋๋ module/Order/Cart.php์์ ์ฒ๋ฆฌ๋ผ.
// module/Order/Cart.php ๋ด๋ถ
class Cart extends OrderCalculation {
/**
* ์ฅ๋ฐ๊ตฌ๋ ์ ๋ณด ์กฐํ (๋ฐฐ์ก๋น ํฌํจ)
*/
public function getCartInfo() {
// ์ฅ๋ฐ๊ตฌ๋ ์ํ ์กฐํ
$cartData = $this->getCartData();
if (empty($cartData)) {
return [];
}
// ๋ฐฐ์ก๋น ๊ณ์ฐ
$deliveryCharge = $this->getDeliveryGroupCharge($cartData);
// ์ด ์ฃผ๋ฌธ ๊ธ์ก ๊ณ์ฐ
$totalGoodsPrice = 0;
$totalDeliveryFee = 0;
foreach ($cartData as $item) {
$totalGoodsPrice += $item['price'] * $item['goodsCnt'];
}
foreach ($deliveryCharge as $charge) {
$totalDeliveryFee += $charge['fee'];
}
return [
'cartData' => $cartData,
'deliveryCharge' => $deliveryCharge,
'totalGoodsPrice' => $totalGoodsPrice,
'totalDeliveryFee' => $totalDeliveryFee,
'totalOrderPrice' => $totalGoodsPrice + $totalDeliveryFee
];
}
/**
* ์ฅ๋ฐ๊ตฌ๋ ๋ฐฐ์ก๋น ๊ทธ๋ฃน ํ์์ฉ ๋ฐ์ดํฐ
*/
public function getDeliveryGroupDisplay() {
$cartInfo = $this->getCartInfo();
$displayData = [];
foreach ($cartInfo['deliveryCharge'] as $groupKey => $charge) {
$group = $charge['group'];
$displayData[] = [
'groupName' => $this->getDeliveryPolicyName($group['deliverySno']),
'goodsList' => $group['goods'],
'goodsPrice' => number_format($group['goodsPrice']),
'deliveryFee' => number_format($charge['fee']),
'freeCondition' => number_format($group['deliveryFreePrice']),
'remainForFree' => max(0, $group['deliveryFreePrice'] - $group['goodsPrice'])
];
}
return $displayData;
}
}
๐ง Step 3: ํ๋ก ํธ์๋ ํ๋ฉด ๊ตฌ์ฑ
๋ฐฑ์๋ ๋ก์ง์ด ์์ฑ๋์์ผ๋ฉด, ์ด์ ์ฌ์ฉ์์๊ฒ ๋ณด์ฌ์ค ํ๋ฉด์ ๋ง๋ค์ด์ผ ํด. ๊ณ ๋๋ชฐ์ ํ ํ๋ฆฟ ํ์ผ์ skin/ ํด๋ ์๋์ ์์ด.
<!-- skin/front/[์คํจ๋ช
]/order/cart.html -->
<div class="cart-delivery-group">
<h3>๋ฐฐ์ก๋น ์๋ด</h3>
<!--{ @deliveryGroup }-->
<div class="delivery-group-item">
<div class="group-header">
<strong>{=groupName}</strong>
<span class="delivery-fee">๋ฐฐ์ก๋น: {=deliveryFee}์</span>
</div>
<div class="group-goods">
<!--{ @goodsList }-->
<div class="goods-item">
<img src="{=imageUrl}" alt="{=goodsNm}">
<span>{=goodsNm}</span>
<span>{=price}์ x {=goodsCnt}๊ฐ</span>
</div>
<!--{ / }-->
</div>
<!--{ @if remainForFree > 0 }-->
<div class="free-delivery-guide">
<p>{=remainForFree}์ ๋ ๊ตฌ๋งคํ์๋ฉด ๋ฌด๋ฃ๋ฐฐ์ก!</p>
<div class="progress-bar">
<div class="progress" style="width: {=progressPercent}%"></div>
</div>
</div>
<!--{ / }-->
</div>
<!--{ / }-->
<div class="total-delivery-fee">
<strong>์ด ๋ฐฐ์ก๋น</strong>
<span>{=totalDeliveryFee}์</span>
</div>
</div>
์ด๋ ๊ฒ ํ๋ฉด ์ฌ์ฉ์๊ฐ ์ฅ๋ฐ๊ตฌ๋์์ ๋ฐฐ์ก ๊ทธ๋ฃน๋ณ๋ก ๋ฌถ์ธ ์ํ๋ค๊ณผ ๊ฐ ๊ทธ๋ฃน์ ๋ฐฐ์ก๋น๋ฅผ ํ๋์ ๋ณผ ์ ์์ด! ๐จ
๐จ ๊ณ ๊ธ ๊ธฐ๋ฅ ๊ตฌํํ๊ธฐ
๊ธฐ๋ณธ์ ์ธ ๋ฌถ์ ๋ฐฐ์ก ๋ก์ง์ ๊ตฌํํ๋ค๋ฉด, ์ด์ ์ข ๋ ๊ณ ๊ธ ๊ธฐ๋ฅ๋ค์ ์ถ๊ฐํด๋ณผ ์ฐจ๋ก์ผ. ์ค์ ์ผํ๋ชฐ์์๋ ๋ค์ํ ์์ธ ์ํฉ๊ณผ ํน์ ์ผ์ด์ค๋ค์ด ์๊ฑฐ๋ ! ๐ช
๐ ๊ธฐ๋ฅ 1: ์ค๋งํธ ๋ฐฐ์ก๋น ์ต์ ํ
๊ณ ๊ฐ์ด ์ฌ๋ฌ ๋ฐฐ์ก ๊ทธ๋ฃน์ ์ํ์ ๋ด์์ ๋, ์ด๋ค ์กฐํฉ์ด ๊ฐ์ฅ ์ ๋ ดํ์ง ์๋์ผ๋ก ๊ณ์ฐํด์ฃผ๋ ๊ธฐ๋ฅ์ด์ผ. ์๋ฅผ ๋ค์ด A๊ทธ๋ฃน ์ํ 2๊ฐ์ B๊ทธ๋ฃน ์ํ 1๊ฐ๋ฅผ ๋ด์๋๋ฐ, A๊ทธ๋ฃน์์ 1๊ฐ๋ง ๋ ์ฌ๋ฉด ๋ฌด๋ฃ๋ฐฐ์ก์ด ๋๋ค๋ฉด ์ด๋ฅผ ์๋ ค์ฃผ๋ ๊ฑฐ์ง!
/**
* ๋ฐฐ์ก๋น ์ต์ ํ ์ ์
*/
public function suggestOptimalDelivery($cartData) {
$suggestions = [];
$deliveryGroups = $this->getDeliveryGroupCharge($cartData);
foreach ($deliveryGroups as $groupKey => $charge) {
$group = $charge['group'];
// ๋ฌด๋ฃ๋ฐฐ์ก๊น์ง ๋จ์ ๊ธ์ก ๊ณ์ฐ
if ($group['goodsPrice'] < $group['deliveryFreePrice']) {
$remainAmount = $group['deliveryFreePrice'] - $group['goodsPrice'];
// ํด๋น ๊ทธ๋ฃน์ ์ถ์ฒ ์ํ ์กฐํ
$recommendGoods = $this->getRecommendGoodsByPrice(
$group['deliverySno'],
$remainAmount
);
if (!empty($recommendGoods)) {
$suggestions[] = [
'groupName' => $this->getDeliveryPolicyName($group['deliverySno']),
'currentFee' => $charge['fee'],
'remainAmount' => $remainAmount,
'recommendGoods' => $recommendGoods,
'message' => number_format($remainAmount) . '์๋ง ๋ ๊ตฌ๋งคํ์๋ฉด ๋ฐฐ์ก๋น ' .
number_format($charge['fee']) . '์์ด ๋ฌด๋ฃ!'
];
}
}
}
return $suggestions;
}
/**
* ๊ฐ๊ฒฉ๋๋ณ ์ถ์ฒ ์ํ ์กฐํ
*/
private function getRecommendGoodsByPrice($deliverySno, $targetPrice) {
// ํด๋น ๋ฐฐ์ก ์ ์ฑ
์ ์ฌ์ฉํ๋ ์ํ ์ค
// ๋ชฉํ ๊ฐ๊ฒฉ ์ดํ์ ์ธ๊ธฐ ์ํ ์กฐํ
$db = \App::load('DB');
$sql = "SELECT goodsNo, goodsNm, goodsPrice, goodsImage
FROM es_goods
WHERE deliverySno = ?
AND goodsPrice <= ?
AND goodsSellFl = 'y'
ORDER BY orderCnt DESC
LIMIT 5";
return $db->query_fetch($sql, [$deliverySno, $targetPrice]);
}
๐ ๊ธฐ๋ฅ 2: ์ค์๊ฐ ๋ฐฐ์ก๋น ๋ฏธ๋ฆฌ๋ณด๊ธฐ
๊ณ ๊ฐ์ด ์ฅ๋ฐ๊ตฌ๋์ ์ํ์ ์ถ๊ฐํ๊ฑฐ๋ ์๋์ ๋ณ๊ฒฝํ ๋๋ง๋ค ์ค์๊ฐ์ผ๋ก ๋ฐฐ์ก๋น๊ฐ ์ด๋ป๊ฒ ๋ณํ๋์ง ๋ณด์ฌ์ฃผ๋ ๊ธฐ๋ฅ์ด์ผ. AJAX๋ฅผ ํ์ฉํด์ ๊ตฌํํ ์ ์์ด!
// JavaScript (jQuery ์ฌ์ฉ)
function updateDeliveryFeePreview() {
var cartItems = [];
// ์ฅ๋ฐ๊ตฌ๋ ์ํ ์ ๋ณด ์์ง
$('.cart-item').each(function() {
cartItems.push({
goodsNo: $(this).data('goods-no'),
goodsCnt: $(this).find('.quantity-input').val(),
deliverySno: $(this).data('delivery-sno')
});
});
// AJAX ์์ฒญ
$.ajax({
url: '/order/ajax_calculate_delivery',
type: 'POST',
data: {
cartItems: JSON.stringify(cartItems)
},
success: function(response) {
if (response.success) {
// ๋ฐฐ์ก๋น ์ ๋ณด ์
๋ฐ์ดํธ
updateDeliveryDisplay(response.deliveryCharge);
// ๋ฌด๋ฃ๋ฐฐ์ก๊น์ง ๋จ์ ๊ธ์ก ํ์
updateFreeDeliveryProgress(response.deliveryCharge);
// ์ด ์ฃผ๋ฌธ๊ธ์ก ์
๋ฐ์ดํธ
$('#total-order-price').text(
number_format(response.totalOrderPrice) + '์'
);
}
}
});
}
// ๋ฐฐ์ก๋น ์ ๋ณด ํ๋ฉด ์
๋ฐ์ดํธ
function updateDeliveryDisplay(deliveryCharge) {
$('.delivery-group-item').each(function(index) {
var groupKey = $(this).data('group-key');
var charge = deliveryCharge[groupKey];
if (charge) {
$(this).find('.delivery-fee').text(
'๋ฐฐ์ก๋น: ' + number_format(charge.fee) + '์'
);
// ๋ฌด๋ฃ๋ฐฐ์ก ๋ฌ์ฑ ์ ํ์
if (charge.fee === 0) {
$(this).find('.free-badge').show();
} else {
$(this).find('.free-badge').hide();
}
}
});
}
// ๋ฌด๋ฃ๋ฐฐ์ก ์งํ๋ฅ ์
๋ฐ์ดํธ
function updateFreeDeliveryProgress(deliveryCharge) {
$('.delivery-group-item').each(function() {
var groupKey = $(this).data('group-key');
var charge = deliveryCharge[groupKey];
if (charge && charge.group.deliveryFreePrice > 0) {
var percent = Math.min(100,
(charge.group.goodsPrice / charge.group.deliveryFreePrice) * 100
);
$(this).find('.progress').css('width', percent + '%');
var remainAmount = Math.max(0,
charge.group.deliveryFreePrice - charge.group.goodsPrice
);
if (remainAmount > 0) {
$(this).find('.remain-amount').text(
number_format(remainAmount) + '์'
);
} else {
$(this).find('.free-delivery-guide').html(
'<strong>๐ ๋ฌด๋ฃ๋ฐฐ์ก ๋ฌ์ฑ!</strong>'
);
}
}
});
}
// ์๋ ๋ณ๊ฒฝ ์ ์ด๋ฒคํธ ๋ฐ์ธ๋ฉ
$(document).on('change', '.quantity-input', function() {
updateDeliveryFeePreview();
});
// ์ํ ์ญ์ ์ ์ด๋ฒคํธ ๋ฐ์ธ๋ฉ
$(document).on('click', '.btn-delete-item', function() {
// ์ํ ์ญ์ ํ
updateDeliveryFeePreview();
});
๐ ๊ธฐ๋ฅ 3: ๋ฐฐ์ก์ง ๋ถ๋ฆฌ ์ฃผ๋ฌธ
ํ๋์ ์ฃผ๋ฌธ์์ ์ฌ๋ฌ ๋ฐฐ์ก์ง๋ก ๋ณด๋ด์ผ ํ๋ ๊ฒฝ์ฐ๋ ์์ด. ์๋ฅผ ๋ค์ด ์ ๋ฌผ์ฉ์ผ๋ก ๊ฐ๊ฐ ๋ค๋ฅธ ์ฌ๋์๊ฒ ๋ณด๋ด๋ ๊ฒฝ์ฐ์ง. ์ด๋๋ ๋ฐฐ์ก์ง๋ณ๋ก ๋ฐฐ์ก๋น๋ฅผ ๋ฐ๋ก ๊ณ์ฐํด์ผ ํด!
/**
* ๋ฐฐ์ก์ง ๋ถ๋ฆฌ ์ฃผ๋ฌธ ์ฒ๋ฆฌ
*/
public function processMultipleDeliveryAddress($orderData) {
$deliveryAddresses = $orderData['deliveryAddresses']; // ๋ฐฐ์ก์ง ๋ชฉ๋ก
$cartItems = $orderData['cartItems'];
$deliveryGroups = [];
// ๋ฐฐ์ก์ง๋ณ๋ก ์ํ ๊ทธ๋ฃนํ
foreach ($cartItems as $item) {
$addressKey = $item['deliveryAddressKey'];
$deliveryKey = $item['deliverySno'] . '_' . $addressKey;
if (!isset($deliveryGroups[$deliveryKey])) {
$deliveryGroups[$deliveryKey] = [
'address' => $deliveryAddresses[$addressKey],
'deliverySno' => $item['deliverySno'],
'items' => [],
'goodsPrice' => 0
];
}
$deliveryGroups[$deliveryKey]['items'][] = $item;
$deliveryGroups[$deliveryKey]['goodsPrice'] += $item['price'] * $item['goodsCnt'];
}
// ๊ฐ ๋ฐฐ์ก์ง๋ณ ๋ฐฐ์ก๋น ๊ณ์ฐ
$totalDeliveryFee = 0;
$deliveryCharges = [];
foreach ($deliveryGroups as $deliveryKey => $group) {
// ๊ธฐ๋ณธ ๋ฐฐ์ก๋น ๊ณ์ฐ
$baseFee = $this->calculateBasicDeliveryFee($group);
// ์ง์ญ๋ณ ์ถ๊ฐ ๋ฐฐ์ก๋น
$extraFee = $this->calculateRegionalExtraFee($group['address']);
$totalFee = $baseFee + $extraFee;
$deliveryCharges[$deliveryKey] = [
'baseFee' => $baseFee,
'extraFee' => $extraFee,
'totalFee' => $totalFee,
'address' => $group['address']
];
$totalDeliveryFee += $totalFee;
}
return [
'deliveryGroups' => $deliveryGroups,
'deliveryCharges' => $deliveryCharges,
'totalDeliveryFee' => $totalDeliveryFee
];
}
/**
* ์ง์ญ๋ณ ์ถ๊ฐ ๋ฐฐ์ก๋น ๊ณ์ฐ (์์ธ)
*/
private function calculateRegionalExtraFee($address) {
$extraFee = 0;
// ์ ์ฃผ๋ ์ฒดํฌ
if (strpos($address['address1'], '์ ์ฃผ') !== false) {
$extraFee += 3000;
}
// ๋์์ฐ๊ฐ ์ฒดํฌ (์ฐํธ๋ฒํธ ๊ธฐ๋ฐ)
$remoteAreaZipCodes = $this->getRemoteAreaZipCodes();
if (in_array($address['zipcode'], $remoteAreaZipCodes)) {
$extraFee += 5000;
}
// ์ฐ๊ฐ์ง์ญ ์ถ๊ฐ ์ฒดํฌ
$mountainKeywords = ['์ฐ', '๋ฉด', '๋ฆฌ'];
foreach ($mountainKeywords as $keyword) {
if (strpos($address['address2'], $keyword) !== false) {
$extraFee += 2000;
break;
}
}
return $extraFee;
}
๐ ์์ฃผ ๋ฐ์ํ๋ ๋ฌธ์ ์ ํด๊ฒฐ ๋ฐฉ๋ฒ
๋ฌถ์ ๋ฐฐ์ก ๋ก์ง์ ๊ตฌํํ๋ค ๋ณด๋ฉด ์ฌ๋ฌ ๊ฐ์ง ๋ฌธ์ ์ ๋ถ๋ชํ๊ฒ ๋ผ. ๋ด๊ฐ ์ค์ ๋ก ๊ฒช์๋ ๋ฌธ์ ๋ค๊ณผ ํด๊ฒฐ ๋ฐฉ๋ฒ์ ๊ณต์ ํ ๊ฒ! ๐
โ ๋ฌธ์ 1: ๋ฐฐ์ก๋น๊ฐ ์ค๋ณต์ผ๋ก ๊ณ์ฐ๋๋ ๊ฒฝ์ฐ
์์ธ: ๋ฐฐ์ก ๊ทธ๋ฃน ํค(groupKey)๋ฅผ ์์ฑํ ๋ ๊ณ ์ ํ์ง ์์ ๊ฐ์ ์ฌ์ฉํ๊ฑฐ๋, ๊ทธ๋ฃนํ ๋ก์ง์์ ์กฐ๊ฑด ์ฒดํฌ๊ฐ ๋๋ฝ๋จ.
ํด๊ฒฐ ๋ฐฉ๋ฒ:
// ์๋ชป๋ ์์
$groupKey = $item['deliverySno']; // ๋ฐฐ์ก ์ ์ฑ
ID๋ง ์ฌ์ฉ
// ์ฌ๋ฐ๋ฅธ ์์
$groupKey = $item['deliverySno'] . '_' .
$item['scmNo'] . '_' .
$item['warehouseCode'] . '_' .
($item['separateDelivery'] ? uniqid() : ''); // ๊ฐ๋ณ ๋ฐฐ์ก ์ํ์ ๋ณ๋ ๊ทธ๋ฃน
// ์ถ๊ฐ๋ก ๋ฐฐ์ก ๋ถ๊ฐ ์กฐํฉ ์ฒดํฌ
if ($this->isNonMixableGoods($item)) {
$groupKey .= '_' . $item['goodsNo']; // ์ํ๋ณ๋ก ๋ณ๋ ๊ทธ๋ฃน
}
โ ๋ฌธ์ 2: ๋ฌด๋ฃ๋ฐฐ์ก ์กฐ๊ฑด์ด ์ ๋๋ก ์ ์ฉ๋์ง ์์
์์ธ: ์ํ ๊ธ์ก ํฉ์ฐ ์ ํ ์ธ์ด๋ ์ฟ ํฐ์ ๊ณ ๋ คํ์ง ์์๊ฑฐ๋, ๊ทธ๋ฃน๋ณ ๊ธ์ก ๊ณ์ฐ์ด ์๋ชป๋จ.
ํด๊ฒฐ ๋ฐฉ๋ฒ:
/**
* ๋ฌด๋ฃ๋ฐฐ์ก ์กฐ๊ฑด ์ฒดํฌ (ํ ์ธ ์ ์ฉ ํ ๊ธ์ก ๊ธฐ์ค)
*/
private function checkFreeDeliveryCondition($group, $discountInfo) {
// ์ํ ๊ธ์ก ํฉ์ฐ
$totalGoodsPrice = 0;
foreach ($group['items'] as $item) {
$itemPrice = $item['price'] * $item['goodsCnt'];
// ์ํ๋ณ ํ ์ธ ์ ์ฉ
if (isset($discountInfo[$item['goodsNo']])) {
$itemPrice -= $discountInfo[$item['goodsNo']]['discountAmount'];
}
$totalGoodsPrice += $itemPrice;
}
// ๊ทธ๋ฃน ์ ์ฒด ํ ์ธ ์ ์ฉ (์ฅ๋ฐ๊ตฌ๋ ์ฟ ํฐ ๋ฑ)
if (isset($discountInfo['groupDiscount'][$group['deliverySno']])) {
$totalGoodsPrice -= $discountInfo['groupDiscount'][$group['deliverySno']];
}
// ๋ฌด๋ฃ๋ฐฐ์ก ์กฐ๊ฑด ์ฒดํฌ
$freeDeliveryPrice = $group['deliveryFreePrice'];
// ๋ฌด๋ฃ๋ฐฐ์ก ์กฐ๊ฑด์ด 0์ด๋ฉด ํญ์ ์ ๋ฃ
if ($freeDeliveryPrice == 0) {
return false;
}
// ์กฐ๊ฑด ๊ธ์ก ์ด์์ด๋ฉด ๋ฌด๋ฃ๋ฐฐ์ก
return ($totalGoodsPrice >= $freeDeliveryPrice);
}
โ ๋ฌธ์ 3: ์๋ ๋ณ๊ฒฝ ์ ๋ฐฐ์ก๋น๊ฐ ์ค์๊ฐ์ผ๋ก ์ ๋ฐ์ดํธ๋์ง ์์
์์ธ: ํ๋ก ํธ์๋์์ ์๋ ๋ณ๊ฒฝ ์ด๋ฒคํธ๋ฅผ ์ ๋๋ก ๊ฐ์งํ์ง ๋ชปํ๊ฑฐ๋, AJAX ์์ฒญ์ด ์คํจํจ.
ํด๊ฒฐ ๋ฐฉ๋ฒ:
// ๋๋ฐ์ด์ฑ์ ์ ์ฉํ ์ค์๊ฐ ์
๋ฐ์ดํธ
var deliveryUpdateTimer;
function scheduleDeliveryUpdate() {
// ๊ธฐ์กด ํ์ด๋จธ ์ทจ์
clearTimeout(deliveryUpdateTimer);
// 0.5์ด ํ ์
๋ฐ์ดํธ (์ฐ์ ์
๋ ฅ ๋ฐฉ์ง)
deliveryUpdateTimer = setTimeout(function() {
updateDeliveryFeePreview();
}, 500);
}
// ์๋ ์
๋ ฅ ์ด๋ฒคํธ
$(document).on('input', '.quantity-input', function() {
// ์
๋ ฅ๊ฐ ๊ฒ์ฆ
var value = parseInt($(this).val());
if (isNaN(value) || value < 1) {
$(this).val(1);
}
scheduleDeliveryUpdate();
});
// +/- ๋ฒํผ ํด๋ฆญ ์ด๋ฒคํธ
$(document).on('click', '.btn-quantity-plus, .btn-quantity-minus', function() {
var $input = $(this).siblings('.quantity-input');
var currentValue = parseInt($input.val());
var newValue;
if ($(this).hasClass('btn-quantity-plus')) {
newValue = currentValue + 1;
} else {
newValue = Math.max(1, currentValue - 1);
}
$input.val(newValue);
scheduleDeliveryUpdate();
});
// AJAX ์๋ฌ ์ฒ๋ฆฌ
function updateDeliveryFeePreview() {
$.ajax({
url: '/order/ajax_calculate_delivery',
type: 'POST',
data: {
cartItems: getCartItemsData()
},
success: function(response) {
if (response.success) {
updateDeliveryDisplay(response.deliveryCharge);
} else {
console.error('๋ฐฐ์ก๋น ๊ณ์ฐ ์คํจ:', response.message);
showErrorMessage('๋ฐฐ์ก๋น๋ฅผ ๊ณ์ฐํ ์ ์์ต๋๋ค.');
}
},
error: function(xhr, status, error) {
console.error('AJAX ์์ฒญ ์คํจ:', error);
showErrorMessage('์ผ์์ ์ธ ์ค๋ฅ๊ฐ ๋ฐ์ํ์ต๋๋ค. ๋ค์ ์๋ํด์ฃผ์ธ์.');
}
});
}
โ ๋ฌธ์ 4: ๋ชจ๋ฐ์ผ์์ ๋ฐฐ์ก๋น ์ ๋ณด๊ฐ ์ ๋๋ก ํ์๋์ง ์์
์์ธ: ๋ฐ์ํ CSS๊ฐ ์ ๋๋ก ์ ์ฉ๋์ง ์์๊ฑฐ๋, ํ ์ด๋ธ ํํ์ ๋ ์ด์์์ ์ฌ์ฉํด์ ๋ชจ๋ฐ์ผ์์ ๊ฐ๋ก ์คํฌ๋กค์ด ๋ฐ์ํจ.
ํด๊ฒฐ ๋ฐฉ๋ฒ:
/* ๋ฐ์ํ ๋ฐฐ์ก๋น ํ์ CSS */
.delivery-group-item {
background: #f8f9fa;
border-radius: 8px;
padding: 15px;
margin-bottom: 15px;
}
.group-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 10px;
flex-wrap: wrap; /* ๋ชจ๋ฐ์ผ์์ ์ค๋ฐ๊ฟ */
}
.delivery-fee {
color: #e74c3c;
font-weight: bold;
font-size: 1.1em;
}
/* ๋ชจ๋ฐ์ผ ์ต์ ํ */
@media (max-width: 768px) {
.group-header {
flex-direction: column;
align-items: flex-start;
}
.delivery-fee {
margin-top: 5px;
font-size: 1em;
}
.group-goods {
display: flex;
flex-direction: column;
gap: 10px;
}
.goods-item {
display: flex;
align-items: center;
gap: 10px;
padding: 10px;
background: white;
border-radius: 5px;
}
.goods-item img {
width: 60px;
height: 60px;
object-fit: cover;
border-radius: 5px;
}
.free-delivery-guide {
padding: 10px;
background: #e8f5e9;
border-radius: 5px;
margin-top: 10px;
}
.progress-bar {
width: 100%;
height: 8px;
background: #ddd;
border-radius: 4px;
overflow: hidden;
margin-top: 8px;
}
.progress {
height: 100%;
background: linear-gradient(90deg, #66bb6a, #4caf50);
transition: width 0.3s ease;
}
}
๐ ํ ์คํธ ๋ฐ ๊ฒ์ฆ ๋ฐฉ๋ฒ
๋ฌถ์ ๋ฐฐ์ก ๋ก์ง์ ๊ตฌํํ๋ค๋ฉด ๋ฐ๋์ ์ฒ ์ ํ ํ ์คํธ๋ฅผ ๊ฑฐ์ณ์ผ ํด. ๋ฐฐ์ก๋น๋ ๋๊ณผ ์ง๊ฒฐ๋๋ ๋ถ๋ถ์ด๋ผ ์์ ์ค๋ฅ๋ ํฐ ๋ฌธ์ ๊ฐ ๋ ์ ์๊ฑฐ๋ ! ๐งช
โ ํ ์คํธ ์ฒดํฌ๋ฆฌ์คํธ
โข ๋จ์ผ ์ํ ์ฃผ๋ฌธ ์ ๋ฐฐ์ก๋น ์ ์ ๊ณ์ฐ
โข ๋์ผ ๋ฐฐ์ก ์ ์ฑ ์ํ 2๊ฐ ์ด์ ์ฃผ๋ฌธ ์ ๋ฌถ์ ๋ฐฐ์ก๋น ์ ์ฉ
โข ๋ค๋ฅธ ๋ฐฐ์ก ์ ์ฑ ์ํ ํผํฉ ์ฃผ๋ฌธ ์ ๊ฐ๊ฐ ๋ฐฐ์ก๋น ๋ถ๊ณผ
โข ๋ฌด๋ฃ๋ฐฐ์ก ์กฐ๊ฑด ๋ฌ์ฑ ์ ๋ฐฐ์ก๋น 0์ ์ฒ๋ฆฌ
โข ๋ฌด๋ฃ๋ฐฐ์ก ์กฐ๊ฑด ๊ธ์ก ์ ํํ ์ผ์น ์
โข ๋ฌด๋ฃ๋ฐฐ์ก ์กฐ๊ฑด ๊ธ์ก๋ณด๋ค 1์ ๋ถ์กฑ ์
โข ์ต์ ์ฃผ๋ฌธ ๊ธ์ก ๊ฒฝ๊ณ๊ฐ
โข ์ต๋ ์๋ ์ ํ ๊ฒฝ๊ณ๊ฐ
โข ์ํ ํ ์ธ ์ ์ฉ ํ ๋ฌด๋ฃ๋ฐฐ์ก ์กฐ๊ฑด ์ฒดํฌ
โข ์ฟ ํฐ ์ฌ์ฉ ํ ๋ฐฐ์ก๋น ๊ณ์ฐ
โข ์ ๋ฆฝ๊ธ ์ฌ์ฉ ์ ๋ฐฐ์ก๋น ์ํฅ ํ์ธ
โข ํ๋ก๋ชจ์ ํ ์ธ๊ณผ ๋ฐฐ์ก๋น ์กฐํฉ
โข ์ ์ฃผ/๋์์ฐ๊ฐ ์ถ๊ฐ ๋ฐฐ์ก๋น
โข ๋์ฅ/๋๋ ์ํ ๋ณ๋ ๋ฐฐ์ก
โข ๋ํ ์ํ ํน๋ณ ๋ฐฐ์ก๋น
โข ์์ฝ ์ํ๊ณผ ์ผ๋ฐ ์ํ ํผํฉ ์ฃผ๋ฌธ
๐งช ์๋ํ ํ ์คํธ ์ฝ๋ ์์
// PHPUnit ํ
์คํธ ์์
class DeliveryCalculationTest extends TestCase {
private $calculator;
protected function setUp(): void {
parent::setUp();
$this->calculator = new OrderCalculation();
}
/**
* ๊ธฐ๋ณธ ๋ฌถ์ ๋ฐฐ์ก ํ
์คํธ
*/
public function testBasicBundleDelivery() {
$cartData = [
[
'goodsNo' => 1,
'price' => 10000,
'goodsCnt' => 1,
'deliverySno' => 1,
'deliveryFee' => 3000,
'deliveryFreePrice' => 50000,
'scmNo' => 1
],
[
'goodsNo' => 2,
'price' => 15000,
'goodsCnt' => 1,
'deliverySno' => 1,
'deliveryFee' => 3000,
'deliveryFreePrice' => 50000,
'scmNo' => 1
]
];
$result = $this->calculator->getDeliveryGroupCharge($cartData);
// ๊ฐ์ ๋ฐฐ์ก ์ ์ฑ
์ด๋ฏ๋ก ๋ฐฐ์ก๋น๋ ํ ๋ฒ๋ง ๋ถ๊ณผ
$this->assertEquals(3000, array_sum(array_column($result, 'fee')));
// ๋ฐฐ์ก ๊ทธ๋ฃน์ 1๊ฐ๋ง ์์ฑ
$this->assertCount(1, $result);
}
/**
* ๋ฌด๋ฃ๋ฐฐ์ก ์กฐ๊ฑด ๋ฌ์ฑ ํ
์คํธ
*/
public function testFreeDeliveryCondition() {
$cartData = [
[
'goodsNo' => 1,
'price' => 30000,
'goodsCnt' => 2,
'deliverySno' => 1,
'deliveryFee' => 3000,
'deliveryFreePrice' => 50000,
'scmNo' => 1
]
];
$result = $this->calculator->getDeliveryGroupCharge($cartData);
// 60,000์์ด๋ฏ๋ก ๋ฌด๋ฃ๋ฐฐ์ก ์กฐ๊ฑด ๋ฌ์ฑ
$this->assertEquals(0, array_sum(array_column($result, 'fee')));
}
/**
* ๋ค์ค ๋ฐฐ์ก ๊ทธ๋ฃน ํ
์คํธ
*/
public function testMultipleDeliveryGroups() {
$cartData = [
[
'goodsNo' => 1,
'price' => 10000,
'goodsCnt' => 1,
'deliverySno' => 1,
'deliveryFee' => 3000,
'deliveryFreePrice' => 50000,
'scmNo' => 1
],
[
'goodsNo' => 2,
'price' => 20000,
'goodsCnt' => 1,
'deliverySno' => 2,
'deliveryFee' => 5000,
'deliveryFreePrice' => 100000,
'scmNo' => 1
]
];
$result = $this->calculator->getDeliveryGroupCharge($cartData);
// ๋ค๋ฅธ ๋ฐฐ์ก ์ ์ฑ
์ด๋ฏ๋ก ๊ฐ๊ฐ ๋ฐฐ์ก๋น ๋ถ๊ณผ
$this->assertEquals(8000, array_sum(array_column($result, 'fee')));
// ๋ฐฐ์ก ๊ทธ๋ฃน์ 2๊ฐ ์์ฑ
$this->assertCount(2, $result);
}
/**
* ์ง์ญ๋ณ ์ถ๊ฐ ๋ฐฐ์ก๋น ํ
์คํธ
*/
public function testRegionalExtraFee() {
$cartData = [
[
'goodsNo' => 1,
'price' => 10000,
'goodsCnt' => 1,
'deliverySno' => 1,
'deliveryFee' => 3000,
'deliveryFreePrice' => 50000,
'scmNo' => 1
]
];
// ์ ์ฃผ ์ง์ญ ์ค์
$address = [
'address1' => '์ ์ฃผํน๋ณ์์น๋ ์ ์ฃผ์',
'zipcode' => '63000'
];
$result = $this->calculator->getDeliveryGroupCharge($cartData, $address);
// ๊ธฐ๋ณธ ๋ฐฐ์ก๋น 3,000์ + ์ ์ฃผ ์ถ๊ฐ 3,000์
$this->assertEquals(6000, array_sum(array_column($result, 'fee')));
}
}
์ด๋ ๊ฒ ์๋ํ๋ ํ ์คํธ๋ฅผ ๋ง๋ค์ด๋๋ฉด ์ฝ๋๋ฅผ ์์ ํ ๋๋ง๋ค ๊ธฐ์กด ๊ธฐ๋ฅ์ด ์ ์ ๋์ํ๋์ง ๋น ๋ฅด๊ฒ ํ์ธํ ์ ์์ด! ๐
๐ ์ฑ๋ฅ ์ต์ ํ ์ ๋ต
๋ฐฐ์ก๋น ๊ณ์ฐ์ ์ฃผ๋ฌธ ํ๋ก์ธ์ค์์ ๋งค์ฐ ๋น๋ฒํ๊ฒ ์ผ์ด๋๋ ์์ ์ด์ผ. ํนํ ์ฅ๋ฐ๊ตฌ๋ ํ์ด์ง์์๋ ์ฌ์ฉ์๊ฐ ์๋์ ๋ณ๊ฒฝํ ๋๋ง๋ค ๊ณ์ฐ์ด ์ด๋ฃจ์ด์ง๊ธฐ ๋๋ฌธ์ ์ฑ๋ฅ ์ต์ ํ๊ฐ ์ ๋ง ์ค์ํด! โก
๐ ์ต์ ํ ๊ธฐ๋ฒ 1: ๋ฐฐ์ก ์ ์ฑ ์บ์ฑ
๋ฐฐ์ก ์ ์ฑ ์ ๋ณด๋ ์์ฃผ ๋ณ๊ฒฝ๋์ง ์์ผ๋๊น ์บ์์ ์ ์ฅํด๋๊ณ ์ฌ์ฉํ๋ฉด DB ์กฐํ๋ฅผ ํฌ๊ฒ ์ค์ผ ์ ์์ด.
/**
* ๋ฐฐ์ก ์ ์ฑ
์บ์ฑ (Redis ์ฌ์ฉ)
*/
class DeliveryPolicyCache {
private $redis;
private $cachePrefix = 'delivery_policy:';
private $cacheTTL = 3600; // 1์๊ฐ
public function __construct() {
$this->redis = new Redis();
$this->redis->connect('127.0.0.1', 6379);
}
/**
* ๋ฐฐ์ก ์ ์ฑ
์กฐํ (์บ์ ์ฐ์ )
*/
public function getDeliveryPolicy($deliverySno) {
$cacheKey = $this->cachePrefix . $deliverySno;
// ์บ์์์ ์กฐํ
$cached = $this->redis->get($cacheKey);
if ($cached !== false) {
return json_decode($cached, true);
}
// DB์์ ์กฐํ
$policy = $this->fetchFromDatabase($deliverySno);
// ์บ์์ ์ ์ฅ
$this->redis->setex(
$cacheKey,
$this->cacheTTL,
json_encode($policy)
);
return $policy;
}
/**
* ๋ฐฐ์ก ์ ์ฑ
์ผ๊ด ์กฐํ
*/
public function getMultipleDeliveryPolicies($deliverySnoList) {
$policies = [];
$missingKeys = [];
// ์บ์์์ ์ผ๊ด ์กฐํ
$cacheKeys = array_map(function($sno) {
return $this->cachePrefix . $sno;
}, $deliverySnoList);
$cachedData = $this->redis->mget($cacheKeys);
foreach ($deliverySnoList as $index => $sno) {
if ($cachedData[$index] !== false) {
$policies[$sno] = json_decode($cachedData[$index], true);
} else {
$missingKeys[] = $sno;
}
}
// ์บ์์ ์๋ ๊ฒ๋ง DB ์กฐํ
if (!empty($missingKeys)) {
$dbPolicies = $this->fetchMultipleFromDatabase($missingKeys);
foreach ($dbPolicies as $sno => $policy) {
$policies[$sno] = $policy;
// ์บ์์ ์ ์ฅ
$this->redis->setex(
$this->cachePrefix . $sno,
$this->cacheTTL,
json_encode($policy)
);
}
}
return $policies;
}
/**
* ์บ์ ๋ฌดํจํ
*/
public function invalidateCache($deliverySno) {
$cacheKey = $this->cachePrefix . $deliverySno;
$this->redis->del($cacheKey);
}
}
๐ ์ต์ ํ ๊ธฐ๋ฒ 2: ์ฟผ๋ฆฌ ์ต์ ํ
๋ฐฐ์ก๋น ๊ณ์ฐ์ ํ์ํ ๋ฐ์ดํฐ๋ฅผ ํ ๋ฒ์ ์ฟผ๋ฆฌ๋ก ๊ฐ์ ธ์ค๋๋ก ์ต์ ํํด์ผ ํด.
/**
* ์ต์ ํ๋ ์ฅ๋ฐ๊ตฌ๋ ๋ฐ์ดํฐ ์กฐํ
*/
public function getOptimizedCartData($memNo) {
$db = \App::load('DB');
// JOIN์ ํ์ฉํ ๋จ์ผ ์ฟผ๋ฆฌ๋ก ๋ชจ๋ ์ ๋ณด ์กฐํ
$sql = "
SELECT
c.cartSno,
c.goodsNo,
c.goodsCnt,
g.goodsNm,
g.goodsPrice as price,
g.goodsWeight as weight,
d.sno as deliverySno,
d.deliveryFee,
d.deliveryFreePrice,
d.deliveryMethod,
d.scmNo,
d.warehouseCode,
s.companyNm as scmName,
CASE
WHEN g.goodsSellFl = 'n' THEN 'soldout'
WHEN g.stockFl = 'y' AND g.totalStock < c.goodsCnt THEN 'shortage'
ELSE 'available'
END as stockStatus
FROM es_cart c
INNER JOIN es_goods g ON c.goodsNo = g.goodsNo
INNER JOIN es_delivery d ON g.deliverySno = d.sno
LEFT JOIN es_scm s ON d.scmNo = s.sno
WHERE c.memNo = ?
AND c.cartSno > 0
ORDER BY d.sno, d.scmNo
";
$cartData = $db->query_fetch($sql, [$memNo]);
return $cartData;
}
๐ ์ต์ ํ ๊ธฐ๋ฒ 3: ํ๋ก ํธ์๋ ์ต์ ํ
๋ถํ์ํ AJAX ์์ฒญ์ ์ค์ด๊ณ , ์ฌ์ฉ์ ๊ฒฝํ์ ๊ฐ์ ํ๋ ๋ฐฉ๋ฒ์ด์ผ.
// ๋๋ฐ์ด์ฑ๊ณผ ์ฐ๋กํ๋ง์ ํ์ฉํ ์ต์ ํ
class DeliveryFeeUpdater {
constructor() {
this.updateTimer = null;
this.lastUpdateTime = 0;
this.minUpdateInterval = 1000; // ์ต์ 1์ด ๊ฐ๊ฒฉ
this.debounceDelay = 500; // 0.5์ด ๋๋ฐ์ด์ค
}
/**
* ๋๋ฐ์ด์ฑ ์ ์ฉ ์
๋ฐ์ดํธ
*/
scheduleUpdate() {
clearTimeout(this.updateTimer);
this.updateTimer = setTimeout(() => {
this.performUpdate();
}, this.debounceDelay);
}
/**
* ์ฐ๋กํ๋ง ์ฒดํฌ ํ ์
๋ฐ์ดํธ
*/
performUpdate() {
const now = Date.now();
const timeSinceLastUpdate = now - this.lastUpdateTime;
if (timeSinceLastUpdate < this.minUpdateInterval) {
// ๋๋ฌด ๋น ๋ฅธ ์์ฒญ์ ์ง์ฐ
setTimeout(() => {
this.performUpdate();
}, this.minUpdateInterval - timeSinceLastUpdate);
return;
}
this.lastUpdateTime = now;
this.updateDeliveryFee();
}
/**
* ์ค์ ๋ฐฐ์ก๋น ์
๋ฐ์ดํธ
*/
updateDeliveryFee() {
const cartData = this.getCartData();
// ๋ก์ปฌ ์บ์ ์ฒดํฌ
const cacheKey = this.generateCacheKey(cartData);
const cached = this.getFromLocalCache(cacheKey);
if (cached && !this.isCacheExpired(cached)) {
this.updateUI(cached.data);
return;
}
// AJAX ์์ฒญ
$.ajax({
url: '/order/ajax_calculate_delivery',
type: 'POST',
data: { cartData: JSON.stringify(cartData) },
success: (response) => {
if (response.success) {
// ๋ก์ปฌ ์บ์์ ์ ์ฅ
this.saveToLocalCache(cacheKey, response, 60); // 60์ด ์บ์
this.updateUI(response);
}
}
});
}
/**
* ๋ก์ปฌ ์คํ ๋ฆฌ์ง ์บ์
*/
getFromLocalCache(key) {
try {
const cached = localStorage.getItem('delivery_' + key);
return cached ? JSON.parse(cached) : null;
} catch (e) {
return null;
}
}
saveToLocalCache(key, data, ttl) {
try {
const cacheData = {
data: data,
timestamp: Date.now(),
ttl: ttl * 1000
};
localStorage.setItem('delivery_' + key, JSON.stringify(cacheData));
} catch (e) {
// ์คํ ๋ฆฌ์ง ์ฉ๋ ์ด๊ณผ ์ ์ค๋๋ ์บ์ ์ญ์
this.clearOldCache();
}
}
isCacheExpired(cached) {
return (Date.now() - cached.timestamp) > cached.ttl;
}
/**
* ์บ์ ํค ์์ฑ
*/
generateCacheKey(cartData) {
const keyData = cartData.map(item =>
`${item.goodsNo}_${item.goodsCnt}`
).join('|');
// ๊ฐ๋จํ ํด์ ์์ฑ
let hash = 0;
for (let i = 0; i < keyData.length; i++) {
hash = ((hash << 5) - hash) + keyData.charCodeAt(i);
hash = hash & hash;
}
return Math.abs(hash).toString(36);
}
}
๐ ์ค์ ํ์ฉ ์ฌ๋ก
์ด๋ก ๊ณผ ์ฝ๋๋ง ๋ด์๋ ๊ฐ์ด ์ ์ฌ ์ ์์ผ๋, ์ค์ ์ผํ๋ชฐ์์ ์ด๋ป๊ฒ ํ์ฉ๋๋์ง ์ฌ๋ก๋ฅผ ๋ค์ด๋ณผ๊ฒ! ๐ช
๐ฑ ์ฌ๋ก 1: ํจ์ ์ผํ๋ชฐ - ๋ธ๋๋๋ณ ๋ฌถ์ ๋ฐฐ์ก
์ฌ๋ฌ ๋ธ๋๋๋ฅผ ์ ์ ์ํจ ํจ์ ์ผํ๋ชฐ์์๋ ๋ธ๋๋(๊ณต๊ธ์ฌ)๋ณ๋ก ๋ฐฐ์ก๋น๋ฅผ ๋ค๋ฅด๊ฒ ์ฑ ์ ํ๋ ๊ฒฝ์ฐ๊ฐ ๋ง์. ์๋ฅผ ๋ค์ด A๋ธ๋๋๋ 3๋ง์ ์ด์ ๋ฌด๋ฃ๋ฐฐ์ก, B๋ธ๋๋๋ 5๋ง์ ์ด์ ๋ฌด๋ฃ๋ฐฐ์ก ์ด๋ฐ ์์ผ๋ก ๋ง์ด์ผ.
/**
* ๋ธ๋๋๋ณ ๋ฐฐ์ก๋น ์ ์ฑ
์ ์ฉ
*/
public function calculateBrandBasedDelivery($cartData) {
$brandGroups = [];
// ๋ธ๋๋(SCM)๋ณ๋ก ๊ทธ๋ฃนํ
foreach ($cartData as $item) {
$brandKey = $item['scmNo'];
if (!isset($brandGroups[$brandKey])) {
$brandGroups[$brandKey] = [
'brandName' => $item['scmName'],
'items' => [],
'totalPrice' => 0,
'deliveryPolicy' => $this->getBrandDeliveryPolicy($item['scmNo'])
];
}
$brandGroups[$brandKey]['items'][] = $item;
$brandGroups[$brandKey]['totalPrice'] += $item['price'] * $item['goodsCnt'];
}
// ๊ฐ ๋ธ๋๋๋ณ ๋ฐฐ์ก๋น ๊ณ์ฐ
$deliveryCharges = [];
foreach ($brandGroups as $brandKey => $group) {
$policy = $group['deliveryPolicy'];
$fee = 0;
// ๋ธ๋๋๋ณ ๋ฌด๋ฃ๋ฐฐ์ก ์กฐ๊ฑด ์ฒดํฌ
if ($group['totalPrice'] < $policy['freeDeliveryPrice']) {
$fee = $policy['deliveryFee'];
}
// ๋ธ๋๋๋ณ ํน๋ณ ํ๋ก๋ชจ์
์ฒดํฌ
if ($this->hasSpecialPromotion($brandKey)) {
$fee = 0; // ํ๋ก๋ชจ์
๊ธฐ๊ฐ ๋ฌด๋ฃ๋ฐฐ์ก
}
$deliveryCharges[$brandKey] = [
'brandName' => $group['brandName'],
'fee' => $fee,
'freeCondition' => $policy['freeDeliveryPrice'],
'remainAmount' => max(0, $policy['freeDeliveryPrice'] - $group['totalPrice'])
];
}
return $deliveryCharges;
}
๐ ์ฌ๋ก 2: ์ํ ์ผํ๋ชฐ - ์จ๋๋๋ณ ๋ฐฐ์ก ๋ถ๋ฆฌ
์ ์ ์ํ์ ํ๋งคํ๋ ์ผํ๋ชฐ์์๋ ์์จ/๋์ฅ/๋๋ ์ํ์ ๊ฐ๊ฐ ๋ค๋ฅธ ๋ฐฐ์ก์ผ๋ก ๋ณด๋ด์ผ ํด. ์ด๋ ์จ๋๋๋ณ๋ก ๋ฐฐ์ก๋น๋ฅผ ๊ณ์ฐํด์ผ ํ์ง.
/**
* ์จ๋๋๋ณ ๋ฐฐ์ก๋น ๊ณ์ฐ
*/
public function calculateTemperatureBasedDelivery($cartData) {
$tempGroups = [
'normal' => ['name' => '์์จ', 'items' => [], 'fee' => 3000],
'cold' => ['name' => '๋์ฅ', 'items' => [], 'fee' => 5000],
'frozen' => ['name' => '๋๋', 'items' => [], 'fee' => 5000]
];
// ์จ๋๋๋ณ ๋ถ๋ฅ
foreach ($cartData as $item) {
$tempType = $this->getGoodsTemperatureType($item['goodsNo']);
if (isset($tempGroups[$tempType])) {
$tempGroups[$tempType]['items'][] = $item;
}
}
// ๊ฐ ์จ๋๋๋ณ ๋ฐฐ์ก๋น ๊ณ์ฐ
$deliveryCharges = [];
foreach ($tempGroups as $tempType => $group) {
if (empty($group['items'])) {
continue; // ํด๋น ์จ๋๋ ์ํ์ด ์์ผ๋ฉด ์คํต
}
$totalPrice = 0;
foreach ($group['items'] as $item) {
$totalPrice += $item['price'] * $item['goodsCnt'];
}
$fee = $group['fee'];
// ์จ๋๋๋ณ ๋ฌด๋ฃ๋ฐฐ์ก ์กฐ๊ฑด (์: 5๋ง์ ์ด์)
if ($totalPrice >= 50000) {
$fee = 0;
}
$deliveryCharges[$tempType] = [
'name' => $group['name'],
'fee' => $fee,
'itemCount' => count($group['items']),
'totalPrice' => $totalPrice
];
}
return $deliveryCharges;
}
/**
* ์ํ์ ์จ๋๋ ์กฐํ
*/
private function getGoodsTemperatureType($goodsNo) {
// ์ํ ์ ๋ณด์์ ์จ๋๋ ์ ๋ณด ์กฐํ
$db = \App::load('DB');
$sql = "SELECT temperatureType FROM es_goods WHERE goodsNo = ?";
$result = $db->query_fetch($sql, [$goodsNo], false);
return $result['temperatureType'] ?? 'normal';
}
๐ช ์ฌ๋ก 3: ๊ฐ๊ตฌ ์ผํ๋ชฐ - ๋ถํผ/๋ฌด๊ฒ ๊ธฐ๋ฐ ๋ฐฐ์ก๋น
๋ํ ๊ฐ๊ตฌ๋ฅผ ํ๋งคํ๋ ์ผํ๋ชฐ์์๋ ์ํ์ ๋ถํผ์ ๋ฌด๊ฒ์ ๋ฐ๋ผ ๋ฐฐ์ก๋น๋ฅผ ์ฐจ๋ฑ ์ ์ฉํด์ผ ํด.
/**
* ๋ถํผ/๋ฌด๊ฒ ๊ธฐ๋ฐ ๋ฐฐ์ก๋น ๊ณ์ฐ
*/
public function calculateVolumeWeightBasedDelivery($cartData) {
$deliveryGroups = [];
foreach ($cartData as $item) {
// ์ํ์ ๋ถํผ ๋ฌด๊ฒ ๊ณ์ฐ
$volumeWeight = $this->calculateVolumeWeight($item);
$actualWeight = $item['weight'] * $item['goodsCnt'];
// ๋ถํผ ๋ฌด๊ฒ์ ์ค์ ๋ฌด๊ฒ ์ค ํฐ ๊ฐ ์ ์ฉ
$chargeableWeight = max($volumeWeight, $actualWeight);
// ๋ฌด๊ฒ ๊ตฌ๊ฐ๋ณ ๋ฐฐ์ก๋น ๊ฒฐ์
$deliveryFee = $this->getDeliveryFeeByWeight($chargeableWeight);
// ์ค์น ์๋น์ค ํ์ ์ฌ๋ถ ์ฒดํฌ
if ($item['needInstallation']) {
$deliveryFee += $item['installationFee'];
}
$deliveryGroups[] = [
'goodsNo' => $item['goodsNo'],
'goodsNm' => $item['goodsNm'],
'chargeableWeight' => $chargeableWeight,
'deliveryFee' => $deliveryFee,
'installationFee' => $item['needInstallation'] ? $item['installationFee'] : 0
];
}
return $deliveryGroups;
}
/**
* ๋ถํผ ๋ฌด๊ฒ ๊ณ์ฐ (๊ฐ๋ก x ์ธ๋ก x ๋์ด / 5000)
*/
private function calculateVolumeWeight($item) {
$volume = $item['width'] * $item['length'] * $item['height'];
return $volume / 5000; // ํ๋ฐฐ์ฌ ๊ธฐ์ค
}
/**
* ๋ฌด๊ฒ ๊ตฌ๊ฐ๋ณ ๋ฐฐ์ก๋น
*/
private function getDeliveryFeeByWeight($weight) {
$weightRanges = [
['max' => 5, 'fee' => 5000],
['max' => 10, 'fee' => 8000],
['max' => 20, 'fee' => 12000],
['max' => 30, 'fee' => 18000],
['max' => PHP_INT_MAX, 'fee' => 25000]
];
foreach ($weightRanges as $range) {
if ($weight <= $range['max']) {
return $range['fee'];
}
}
return 25000; // ๊ธฐ๋ณธ๊ฐ
}
์ด๋ฐ ์์ผ๋ก ์ ์ข ๊ณผ ์ํ ํน์ฑ์ ๋ง๊ฒ ๋ฐฐ์ก๋น ๋ก์ง์ ์ปค์คํฐ๋ง์ด์งํ ์ ์์ด! ์ค์ ๋ก ์ฌ๋ฅ๋ท์์๋ ์ด๋ฐ ๋ง์ถคํ ๋ฐฐ์ก ๋ก์ง ๊ฐ๋ฐ ์๋ขฐ๊ฐ ์ ๋ง ๋ง์ด ๋ค์ด์. ๊ฐ ์ผํ๋ชฐ๋ง๋ค ํน์ํ ์๊ตฌ์ฌํญ์ด ์๊ธฐ ๋ง๋ จ์ด๊ฑฐ๋ ! ๐
๐ ๋ณด์ ๊ณ ๋ ค์ฌํญ
๋ฐฐ์ก๋น ๊ณ์ฐ์ ๊ธ์ก๊ณผ ๊ด๋ จ๋ ๋ฏผ๊ฐํ ๋ถ๋ถ์ด๋ผ ๋ณด์์๋ ์ ๊ฒฝ ์จ์ผ ํด. ์ ์์ ์ธ ์ฌ์ฉ์๊ฐ ๋ฐฐ์ก๋น๋ฅผ ์กฐ์ํ๋ ค๊ณ ์๋ํ ์ ์๊ฑฐ๋ ! ๐ก๏ธ
โ ๏ธ ์ฃผ์ ๋ณด์ ์ํ
๐ ๋ณด์ ๋์ฑ
/**
* ์๋ฒ ์ฌ์ด๋ ๋ฐฐ์ก๋น ๊ฒ์ฆ
*/
public function validateDeliveryFee($orderData) {
// 1. ํด๋ผ์ด์ธํธ์์ ์ ์ก๋ ๋ฐฐ์ก๋น
$clientDeliveryFee = $orderData['deliveryFee'];
// 2. ์๋ฒ์์ ์ฌ๊ณ์ฐํ ๋ฐฐ์ก๋น
$serverDeliveryFee = $this->recalculateDeliveryFee($orderData);
// 3. ๋ ๊ฐ์ด ์ผ์นํ๋์ง ๊ฒ์ฆ
if ($clientDeliveryFee != $serverDeliveryFee) {
// ๋ถ์ผ์น ์ ๋ก๊ทธ ๊ธฐ๋ก
$this->logSecurityAlert([
'type' => 'delivery_fee_mismatch',
'client_fee' => $clientDeliveryFee,
'server_fee' => $serverDeliveryFee,
'order_data' => $orderData,
'user_ip' => $_SERVER['REMOTE_ADDR'],
'timestamp' => date('Y-m-d H:i:s')
]);
// ์๋ฒ ๊ณ์ฐ ๊ฐ์ผ๋ก ๊ฐ์ ์ ์ฉ
$orderData['deliveryFee'] = $serverDeliveryFee;
// ์ฌ์ฉ์์๊ฒ ์๋ฆผ
throw new Exception('๋ฐฐ์ก๋น๊ฐ ๋ณ๊ฒฝ๋์์ต๋๋ค. ๋ค์ ํ์ธํด์ฃผ์ธ์.');
}
return true;
}
/**
* ์ฃผ๋ฌธ ๋ฐ์ดํฐ ๋ฌด๊ฒฐ์ฑ ๊ฒ์ฆ
*/
public function verifyOrderIntegrity($orderData) {
// ํด์ ๊ธฐ๋ฐ ๋ฌด๊ฒฐ์ฑ ์ฒดํฌ
$expectedHash = $this->generateOrderHash($orderData);
$receivedHash = $orderData['integrity_hash'] ?? '';
if ($expectedHash !== $receivedHash) {
$this->logSecurityAlert([
'type' => 'order_data_tampering',
'expected_hash' => $expectedHash,
'received_hash' => $receivedHash,
'user_ip' => $_SERVER['REMOTE_ADDR']
]);
throw new Exception('์ฃผ๋ฌธ ์ ๋ณด๊ฐ ๋ณ์กฐ๋์์ต๋๋ค.');
}
return true;
}
/**
* ์ฃผ๋ฌธ ๋ฐ์ดํฐ ํด์ ์์ฑ
*/
private function generateOrderHash($orderData) {
// ์ค์ ํ๋๋ง ์ถ์ถ
$hashData = [
'goods' => $orderData['goods'],
'delivery_fee' => $orderData['deliveryFee'],
'total_price' => $orderData['totalPrice'],
'timestamp' => $orderData['timestamp']
];
// ์ ๋ ฌ ํ JSON ์ธ์ฝ๋ฉ
ksort($hashData);
$jsonData = json_encode($hashData);
// HMAC ํด์ ์์ฑ (๋น๋ฐํค ์ฌ์ฉ)
$secretKey = \Config::get('security.order_hash_key');
return hash_hmac('sha256', $jsonData, $secretKey);
}
/**
* Rate Limiting (API ํธ์ถ ์ ํ)
*/
public function checkRateLimit($userId) {
$redis = new Redis();
$redis->connect('127.0.0.1', 6379);
$key = 'rate_limit:delivery_calc:' . $userId;
$limit = 30; // 1๋ถ๋น 30ํ
$window = 60; // 60์ด
$current = $redis->incr($key);
if ($current === 1) {
$redis->expire($key, $window);
}
if ($current > $limit) {
throw new Exception('์์ฒญ์ด ๋๋ฌด ๋ง์ต๋๋ค. ์ ์ ํ ๋ค์ ์๋ํด์ฃผ์ธ์.');
}
return true;
}
โข HTTPS ํ์ ์ฌ์ฉ์ผ๋ก ๋ฐ์ดํฐ ์ ์ก ์ํธํ
โข CSRF ํ ํฐ์ผ๋ก ์์กฐ ์์ฒญ ๋ฐฉ์ง
โข ์ธ์ ํ์์์ ์ค์ ์ผ๋ก ํ์ด์ฌํน ์ํ ๊ฐ์
โข ์์ฌ์ค๋ฌ์ด ํ๋์ ์๋์ผ๋ก ๋ก๊ทธ ๊ธฐ๋ก ๋ฐ ์๋ฆผ
๐ ๋ง๋ฌด๋ฆฌํ๋ฉฐ
์ฌ๊ธฐ๊น์ง ๊ณ ๋๋ชฐ์์ ์ํ ๋ฌถ์ ๋ฐฐ์ก ๋ก์ง์ ๊ตฌํํ๋ ๋ฐฉ๋ฒ์ ๋ํด ์์ธํ ์์๋ดค์ด! ์ฒ์์๋ ๋ณต์กํด ๋ณด์ผ ์ ์์ง๋ง, ๋จ๊ณ๋ณ๋ก ์ฐจ๊ทผ์ฐจ๊ทผ ๊ตฌํํ๋ค ๋ณด๋ฉด ์๊ฐ๋ณด๋ค ์ด๋ ต์ง ์์. ๐
ํต์ฌ์ ์ ๋ฆฌํ์๋ฉด:
๋ฐฐ์ก ์ ์ฑ , ์ถ๋ฐ์ง, ์ํ ํน์ฑ์ ๋ฐ๋ผ ์ ํํ๊ฒ ๊ทธ๋ฃน์ ๋๋๋ ๊ฒ ๊ธฐ๋ณธ์ด์ผ.
2. ์์ธ ์ฒ๋ฆฌ๋ฅผ ๊ผผ๊ผผํ๊ฒ
๋ฌด๋ฃ๋ฐฐ์ก ์กฐ๊ฑด, ์ง์ญ๋ณ ์ถ๊ฐ ๋ฐฐ์ก๋น, ํน์ ์ํ ๋ฑ ๋ค์ํ ์ผ์ด์ค๋ฅผ ๊ณ ๋ คํด์ผ ํด.
3. ์ฑ๋ฅ ์ต์ ํ๋ ํ์
์บ์ฑ, ์ฟผ๋ฆฌ ์ต์ ํ, ํ๋ก ํธ์๋ ์ต์ ํ๋ก ์ฌ์ฉ์ ๊ฒฝํ์ ๊ฐ์ ํด์ผ ํด.
4. ๋ณด์์ ์ ๋ ๊ฐ๊ณผํ์ง ๋ง
์๋ฒ ์ฌ์ด๋ ๊ฒ์ฆ, ๋ฌด๊ฒฐ์ฑ ์ฒดํฌ, Rate Limiting์ผ๋ก ์ ์์ ์ธ ์๋๋ฅผ ์ฐจ๋จํด์ผ ํด.
5. ํ ์คํธ๋ ์ฒ ์ ํ๊ฒ
๋ค์ํ ์๋๋ฆฌ์ค๋ฅผ ํ ์คํธํ๊ณ , ์๋ํ๋ ํ ์คํธ ์ฝ๋๋ฅผ ์์ฑํด๋๋ ๊ฒ ์ข์.
๋ฐฐ์ก๋น ๋ก์ง์ ํ ๋ฒ ์ ๋๋ก ๊ตฌํํด๋๋ฉด ์ผํ๋ชฐ ์ด์์ด ์ ๋ง ํธํด์ ธ. ๊ณ ๊ฐ๋ค๋ ๋ช ํํ ๋ฐฐ์ก๋น ์๋ด๋ฅผ ๋ฐ์ผ๋ฉด ์ ๋ขฐ๋๊ฐ ๋์์ง๊ณ , ๊ฒฐ๊ณผ์ ์ผ๋ก ๊ตฌ๋งค ์ ํ์จ๋ ์ฌ๋ผ๊ฐ๊ฑฐ๋ ! ๐ช
ํน์ ๊ตฌํํ๋ค๊ฐ ๋งํ๋ ๋ถ๋ถ์ด ์๊ฑฐ๋, ํน์ํ ์๊ตฌ์ฌํญ์ด ์๋ค๋ฉด ์ ๋ฌธ๊ฐ์ ๋์์ ๋ฐ๋ ๊ฒ๋ ์ข์ ๋ฐฉ๋ฒ์ด์ผ. ์ฌ๋ฅ๋ท ๊ฐ์ ํ๋ซํผ์์ ์ผํ๋ชฐ ๊ฐ๋ฐ ์ ๋ฌธ๊ฐ๋ฅผ ์ฐพ์๋ณด๋ ๊ฒ๋ ์ถ์ฒํด!
์, ์ด์ ๋น์ ์ ์ผํ๋ชฐ์ ์๋ฒฝํ ๋ฌถ์ ๋ฐฐ์ก ์์คํ ์ ๊ตฌ์ถํด๋ณด์! ํ์ดํ ! ๐โจ
๊ด๋ จ ํค์๋
๋๊ธ ์์ฑ
์ด ๊ธ์ ๋ํ ์ฌ๋ฌ๋ถ์ ์๊ฐ์ ๋ค๋ ค์ฃผ์ธ์
๋ก๊ทธ์ธ์ด ํ์ํฉ๋๋ค
๋๊ธ์ ์์ฑํ๋ ค๋ฉด ๋จผ์ ๋ก๊ทธ์ธํด์ฃผ์ธ์.
๋๊ธ 0
์ง์์ธ์ ์ฒ - ์ง์ ์ฌ์ฐ๊ถ ๋ณดํธ ๊ณ ์ง
์ง์ ์ฌ์ฐ๊ถ ๋ณดํธ ๊ณ ์ง
- ์ ์๊ถ ๋ฐ ์์ ๊ถ: ๋ณธ ์ปจํ ์ธ ๋ ์ฌ๋ฅ๋ท์ ๋ ์ AI ๊ธฐ์ ๋ก ์์ฑ๋์์ผ๋ฉฐ, ๋ํ๋ฏผ๊ตญ ์ ์๊ถ๋ฒ ๋ฐ ๊ตญ์ ์ ์๊ถ ํ์ฝ์ ์ํด ๋ณดํธ๋ฉ๋๋ค.
- AI ์์ฑ ์ปจํ ์ธ ์ ๋ฒ์ ์ง์: ๋ณธ AI ์์ฑ ์ปจํ ์ธ ๋ ์ฌ๋ฅ๋ท์ ์ง์ ์ฐฝ์๋ฌผ๋ก ์ธ์ ๋๋ฉฐ, ๊ด๋ จ ๋ฒ๊ท์ ๋ฐ๋ผ ์ ์๊ถ ๋ณดํธ๋ฅผ ๋ฐ์ต๋๋ค.
- ์ฌ์ฉ ์ ํ: ์ฌ๋ฅ๋ท์ ๋ช ์์ ์๋ฉด ๋์ ์์ด ๋ณธ ์ปจํ ์ธ ๋ฅผ ๋ณต์ , ์์ , ๋ฐฐํฌ, ๋๋ ์์ ์ ์ผ๋ก ํ์ฉํ๋ ํ์๋ ์๊ฒฉํ ๊ธ์ง๋ฉ๋๋ค.
- ๋ฐ์ดํฐ ์์ง ๊ธ์ง: ๋ณธ ์ปจํ ์ธ ์ ๋ํ ๋ฌด๋จ ์คํฌ๋ํ, ํฌ๋กค๋ง, ๋ฐ ์๋ํ๋ ๋ฐ์ดํฐ ์์ง์ ๋ฒ์ ์ ์ฌ์ ๋์์ด ๋ฉ๋๋ค.
- AI ํ์ต ์ ํ: ์ฌ๋ฅ๋ท์ AI ์์ฑ ์ปจํ ์ธ ๋ฅผ ํ AI ๋ชจ๋ธ ํ์ต์ ๋ฌด๋จ ์ฌ์ฉํ๋ ํ์๋ ๊ธ์ง๋๋ฉฐ, ์ด๋ ์ง์ ์ฌ์ฐ๊ถ ์นจํด๋ก ๊ฐ์ฃผ๋ฉ๋๋ค.
์์ง ๋๊ธ์ด ์์ต๋๋ค.