결제 섹션 구현
ㆍProject Diary/React + Firebase (Snack ShoppingMall)
Point
- 배송비와 총 주문 금액 계산
- 사용자 정보 괸리 및 변경
- 주문지 선택 및 초기화
- 다음 우편번호 api 연동
- 상품 재고 업데이트
1. 배송비와 총 주문 금액 계산
// 배송비 기본값 설정 및 총 주문 금액 초기화
const [deliveryFee, setDeliveryFee] = useState(700); // 기본 배송비를 700으로 설정
const [total, setTotal] = useState(0); // 총 주문 금액을 0으로 초기화
const [totalDeliveryPrice, setTotalDeliveryPrice] = useState(0); // 총 주문 금액 + 배송비를 0으로 초기화
- useState 훅을 사용하여 배송비, 총 주문 금액, 총 주문 금액 + 배송비를 초기화합니다.
- useEffect 훅을 사용하여 product가 변경될 때마다 총 주문 금액과 배송비를 계산합니다.
- 총 가격이 50,000원 이상일 경우 배송비를 무료로 설정합니다.
useEffect(() => {
const totalPrice = product.reduce(
(acc, item) => acc + parseInt(item.product.price) * parseInt(item.qty),
0
);
setTotal(totalPrice); // 총 주문 금액 설정
if (totalPrice >= 50000) {
setDeliveryFee(0); // 총 가격이 50000원 이상일 경우 배송비 무료
} else {
setDeliveryFee(700); // 그렇지 않으면 배송비 700원 설정
}
setTotalDeliveryPrice(totalPrice + deliveryFee); // 총 주문 금액 + 배송비 설정
}, [product, deliveryFee]); // product와 deliveryFee가 변경될 때마다 실행
2. 사용자 정보 관리 및 변경
const [userInfo, setUserInfo] = useState({
userId: user.userId, // 사용자 ID
userIrum: user.userIrum, // 사용자 이름
handphone: user.handphone, // 사용자 휴대전화
zipCode: user.zipCode, // 사용자 우편번호
addr1: user.addr1, // 사용자 주소1
addr2: user.addr2, // 사용자 주소2
});
- useState 훅을 사용하여 사용자 정보를 초기화합니다.
- handleChange 함수는 사용자 입력 변경을 감지하고 userInfo 상태를 업데이트합니다.
const handleChange = (e) => {
const { value, name } = e.target; // 이벤트 대상의 이름과 값을 가져옴
setUserInfo((userInfo) => ({ ...userInfo, [name]: value })); // 해당 이름에 맞는 값을 업데이트
};
3. 주문지 선택 및 초기화
const [placeType, setPlaceType] = useState("default"); // 주문지 선택 타입을 default로 설정
const placeTypeChange = (type) => {
setPlaceType(type); // 주문지 선택 타입 변경
};
- placeType 상태를 사용하여 기본 주소와 새 주소 입력을 관리합니다.
- placeTypeChange 함수는 주문지 타입을 변경합니다.
const onReset = (type) => {
if (type === "default") {
setUserInfo({
userId: user.userId, // 사용자 ID
userIrum: user.userIrum, // 사용자 이름
handphone: user.handphone, // 사용자 휴대전화
zipCode: user.zipCode, // 사용자 우편번호
addr1: user.addr1, // 사용자 주소1
addr2: user.addr2, // 사용자 주소2
});
} else {
setUserInfo({
userId: "", // 사용자 ID 초기화
userIrum: "", // 사용자 이름 초기화
handphone: "", // 사용자 휴대전화 초기화
zipCode: "", // 사용자 우편번호 초기화
addr1: "", // 사용자 주소1 초기화
addr2: "", // 사용자 주소2 초기화
});
}
};
- onReset 함수는 기본 주소 또는 새 주소로 사용자 정보를 초기화합니다.
4. 다음 우편번호 API 연동
useEffect(() => {
window.openDaumPostcode = () => {
new window.daum.Postcode({
oncomplete: (data) => {
let fullAddr = ""; // 최종 주소 변수
let extraAddr = ""; // 조합형 주소 변수
if (data.userSelectedType === "R") {
fullAddr = data.roadAddress; // 도로명 주소
} else {
fullAddr = data.jibunAddress; // 지번 주소
}
if (data.userSelectedType === "R") {
if (data.bname !== "") {
extraAddr += data.bname; // 법정동명이 있을 경우 추가
}
if (data.buildingName !== "") {
extraAddr += extraAddr !== "" ? ", " + data.buildingName : data.buildingName; // 건물명이 있을 경우 추가
}
fullAddr += extraAddr !== "" ? " (" + extraAddr + ")" : ""; // 조합형 주소를 최종 주소에 추가
}
setUserInfo((prevState) => ({
...prevState,
zipCode: data.zonecode, // 우편번호 설정
addr1: fullAddr, // 주소 설정
}));
mAddressSubRef.current.focus(); // 상세 주소 입력으로 포커스 이동
},
}).open();
};
}, []);
- useEffect 훅을 사용하여 다음 우편번호 API를 연동합니다.
- 사용자가 주소를 선택하면 userInfo 상태를 업데이트합니다.
5. 모달 상태 및 네비게이션
const Modal = ({ modalOpen, onReset, product, qty }) => {
const navigate = useNavigate(); // 네비게이션 훅 사용
const user = useSelector((state) => state.members.user); // 리덕스에서 사용자 정보 가져오기
const onBuy = () => {
if (!user) {
alert("로그인을 하십시오."); // 로그인하지 않은 경우 알림
sessionStorage.setItem("previousUrl", "/payment"); // 이전 URL 저장
sessionStorage.setItem(
"choiceProduct",
JSON.stringify({ product: [{ product, qty }] })
);
navigate("/login"); // 로그인 페이지로 이동
} else {
navigate("/payment", { state: { product: [{ product, qty }] } }); // 결제 페이지로 이동
}
};
const onCart = () => {
navigate("/cart"); // 장바구니 페이지로 이동
};
return (
<ModalBlock className={modalOpen.open && "on"}>
{modalOpen.what === "buy" ? (
<div className="content">
<h2>확인</h2>
<p>바로구매를 진행하시겠습니까?</p>
<div className="btn">
<button onClick={onBuy}>확인</button>
<button onClick={onReset}>취소</button>
</div>
</div>
) : (
<div className="content">
<h2>확인</h2>
<p>
상품이 장바구니에 담겼습니다.
<br />
바로 확인 하시겠습니까?
</p>
<div className="btn">
<button onClick={onCart}>확인</button>
<button onClick={onReset}>계속쇼핑</button>
</div>
</div>
)}
</ModalBlock>
);
};
- onBuy 함수는 사용자가 로그인하지 않았을 경우 로그인 페이지로 리다이렉트하고, 로그인했을 경우 결제 페이지로 이동합니다.
6. 상품 재고 업데이트
6-1 상품 재고를 업데이트하는 리덕스
const productSlice = createSlice({
name: "products",
initialState: {
products: [],
carts: [],
cartsCount: 0,
},
reducers: {
initProducts(state, action) {
state.products = action.payload;
},
initCarts(state, action) {
state.carts = action.payload;
state.cartsCount = action.payload.length;
},
updateProductStock(state, action) {
const { productId, quantity } = action.payload;
const product = state.products.find(p => p.key === productId);
if (product) {
product.stock = product.stock - quantity;
}
},
},
});
export const { initProducts, initCarts, updateProductStock } = productSlice.actions;
6-2 상품 재고 업데이트 함수
export const updateStock = (productId, quantity) => async (dispatch) => {
try {
const productRef = kuwazawa_productDB.child(productId);
productRef.once('value', (snapshot) => {
const product = snapshot.val();
if (product && product.stock >= quantity) {
productRef.update({ stock: product.stock - quantity });
dispatch(updateProductStock({ productId, quantity }));
} else {
console.error("재고 부족");
}
});
} catch (error) {
console.error("Error updating stock:", error);
}
};
6-3 결제 및 재고 업데이트 통합
const handlePurchase = (productId, quantity) => {
// 결제 로직 수행
dispatch(updateStock(productId, quantity));
};
// 구매 버튼 클릭 시 실행되는 함수
const onBuyClick = (productId, quantity) => {
handlePurchase(productId, quantity);
};
이렇게 하면 결제 과정에서 상품 재고를 실시간으로 업데이트할 수 됩니다.