next.js
์์ recoil
๊ณผ iamport
๋ฅผ ์ด์ฉํ ํ
์คํธ ๊ฒฐ์ ๊ธฐ๋ฅ ๊ตฌํ์
๋๋ค.
iamport
์ ๋ํ ๋ด์ฉ์ ๊ณต์ ํ์ด์ง์ ๋ค์ด๊ฐ๋ฉด ํ๊ตญ์ด๋ก ๊ฐ ์ํฉ์ ๋ง๋ ๋ฉ๋ด์ผ์ด ์์ผ๋ ์ฐธ๊ณ ํ์๋ฉด ๋ฉ๋๋ค.
iamport
์ ํ์
์ ๋ํ ์ฝ๋๋ ์ฌ๊ธฐ(velog ํฌ์คํธ)๋ฅผ ์ฐธ๊ณ ํ์ต๋๋ค.
๋์ ๋ฐฉ์
- ๋ก๊ทธ์ธํ ์ ์ ๊ฐ ์ํ ๊ตฌ๋งค ๋ฒํผ ํด๋ฆญ
- ํด๋น ์ ์ ๊ฐ ๋ฑ๋กํ ๋ฐฐ์ก์ง์ค์ ํ๋ ์ ํ
recoil
์ ๊ตฌ๋งค์ ํ์ํ ์ ๋ณด๋ค ๋ฑ๋ก ( pg
, amount
, buyer_tel
๋ฑ ๋จ, ์ฌ๋ฌ ์ํ์ ํ๋ฒ์ ๊ฒฐ์ ํ ์ ์์ผ๋ ๊ณตํต ์ ๋ณด(orderData
)์ ๊ฐ๋ณ ์ ๋ณด(singleData
)๋ฅผ ๋ถ๋ฆฌํด์ ์ ์ฅํจ )
- ๊ฒฐ์ ํ์ด์ง๋ก ์ด๋ ( ๊ฒฐ์ ๋ฐฉ๋ฒ ์ ํ... ํ์ฌ๋
kakaopay
or toss
)
- ๊ฒฐ์ ์ ํ์ํ ์ ๋ณด์ ํจ๊ป
iamport
์ ๊ฒฐ์ ์์ฒญ
- ์ ํํ ๊ฒฐ์ ๋ฐฉ๋ฒ๋๋ก ๊ฒฐ์ ( ์ค์ ๊ฒฐ์ ๊ฐ ์๋ ํ
์คํธ์ฉ ๊ฒฐ์ )
- ๊ฒฐ์ ๊ฐ ์ฑ๊ณตํ๋ฉด ๊ฒฐ๊ด๊ฐ์ผ๋ก
DB
์ปฌ๋ผ ์ถ๊ฐ
- ๊ฒฐ์ ๊ฒฐ๊ณผ ํ
์ด๋ธ์ ์ด์ฉํด์ ๊ตฌ๋งค ๋ชฉ๋ก ํ์ด์ง ๊ตฌ์ฑ
์ฝ๋
import type { ApiCreateOrderBody } from "@src/types";
interface RequestPayAdditionalParams {
digital?: boolean;
vbank_due?: string;
m_redirect_url?: string;
app_scheme?: string;
biz_num?: string;
}
interface Display {
card_quota?: number[];
}
interface CustomData {
residence: string;
message: string;
singleData: ApiCreateOrderBody["singleData"];
}
export interface RequestPayParams extends RequestPayAdditionalParams {
pg: "kakaopay" | "tosspay";
pay_method: "kakaopay" | "tosspay" | string;
escrow?: boolean;
merchant_uid: string;
name?: string;
amount: number;
custom_data: CustomData;
tax_free?: number;
currency?: string;
language?: string;
buyer_name?: string;
buyer_tel: string;
buyer_email?: string;
buyer_addr?: string;
buyer_postcode?: string;
notice_url?: string | string[];
display?: Display;
}
interface RequestPayAdditionalResponse {
apply_num?: string;
vbank_num?: string;
vbank_name?: string;
vbank_holder?: string | null;
vbank_date?: number;
}
interface RequestPayResponse extends RequestPayAdditionalResponse {
success: boolean;
error_code: string;
error_msg: string;
imp_uid: string | null;
merchant_uid: string;
pay_method?: string;
paid_amount?: number;
status?: string;
name?: string;
pg_provider?: string;
pg_tid?: string;
buyer_name?: string;
buyer_email?: string;
buyer_tel?: string;
buyer_addr?: string;
buyer_postcode?: string;
custom_data: CustomData;
paid_at?: number;
receipt_url?: string;
}
type RequestPayResponseCallback = (response: RequestPayResponse) => void;
export interface Iamport {
init: (accountID: string) => void;
request_pay: (
params: RequestPayParams,
callback?: RequestPayResponseCallback
) => void;
}
import { useCallback, useEffect } from "react";
import { useRouter } from "next/router";
import { useRecoilValue } from "recoil";
import { toast } from "react-toastify";
import apiService from "@src/api";
import stateService from "@src/states";
import HeadInfo from "@src/components/common/HeadInfo";
import Nav from "@src/components/common/Nav";
import Support from "@src/components/common/Support";
import Tool from "@src/components/common/Tool";
import type { Iamport } from "@src/types";
import { AxiosError } from "axios";
declare global {
interface Window {
IMP: Iamport;
}
}
const Payment = () => {
const router = useRouter();
useEffect(() => {
toast.info("ํ
์คํธ์ฉ ๊ฒฐ์ ์ด๋ฉฐ, ์ค์ ๋ก ๊ฒฐ์ ๊ฐ ๋์ง ์์ต๋๋ค.");
const jquery = document.createElement("script");
jquery.src = "https://code.jquery.com/jquery-1.12.4.min.js";
const iamport = document.createElement("script");
iamport.src = "https://cdn.iamport.kr/js/iamport.payment-1.1.8.js";
document.head.appendChild(jquery);
document.head.appendChild(iamport);
return () => {
document.head.removeChild(jquery);
document.head.removeChild(iamport);
};
}, []);
const paymentData = useRecoilValue(stateService.paymentService.productToPayment);
const onPayment = useCallback(
(pg: "kakaopay" | "tosspay") => () => {
if (!process.env.NEXT_PUBLIC_IAMPORT_CODE)
return toast.error("iamport์ ๊ฐ๋งน์ ์๋ณ์ฝ๋๊ฐ ์์ต๋๋ค.");
if (!paymentData) {
toast.error(
"๊ฒฐ์ ํ ์ํ์ ์ ๋ณด๊ฐ ์กด์ฌํ์ง ์์ต๋๋ค. ๋ฉ์ธ ํ๋ฉด์ผ๋ก ์ด๋๋ฉ๋๋ค."
);
return router.push("/");
}
window.IMP.init(process.env.NEXT_PUBLIC_IAMPORT_CODE);
window.IMP.request_pay(
{ ...paymentData, pg, pay_method: "card" },
async (rsp) => {
if (
!rsp.buyer_name ||
!rsp.buyer_addr ||
!rsp.paid_amount ||
!rsp.buyer_email ||
!rsp.buyer_tel ||
!rsp.pg_provider
)
return toast.warning("๊ฒฐ์ ์ ํ์ํ ๋ฐ์ดํฐ๊ฐ ๋ถ์กฑํฉ๋๋ค.");
if (rsp.success) {
try {
await apiService.orderService.apiCreateOrder({
singleData: rsp.custom_data.singleData,
orderData: {
name: rsp.buyer_name,
address: rsp.buyer_addr,
residence: rsp.custom_data.residence,
message: rsp.custom_data.message,
amount: rsp.paid_amount,
email: rsp.buyer_email,
phone: rsp.buyer_tel,
provider: rsp.pg_provider,
},
});
toast.success(
"๊ฒฐ์ ๊ฐ ์๋ฃ๋์์ต๋๋ค. 2์ด๋ค์ ๊ฒฐ์ ๋ด์ญ ํ์ด์ง๋ก ์ด๋ํฉ๋๋ค.",
{ autoClose: 2000 }
);
setTimeout(() => router.push("/information/order"), 2000);
} catch (error) {
console.error("error >> ", error);
if (error instanceof AxiosError) {
toast.error(error.response?.data.message);
} else {
toast.error("์ ์ ์๋ ๋ฌธ์ ๋ก ์ธํด ๊ฒฐ์ ์ ์คํจํ์ต๋๋ค.");
}
}
} else {
toast.error("๊ฒฐ์ ์ ์คํจํ์ต๋๋ค. " + rsp.error_msg, {
autoClose: 2000,
});
}
}
);
},
[router, paymentData]
);
return (
<>
<HeadInfo
title="BleShop - ๊ฒฐ์ "
description="BleShop์ ๊ฒฐ์ ํ์ด์ง์
๋๋ค."
/>
<article className="pt-4 space-y-4">
<Nav.TitleNav title="๋์๊ฐ๊ธฐ" />
<Support.Background className="space-y-2 sm:space-y-4" hasPadding>
<Support.Title text="๊ฒฐ์ " />
<ul className="space-y-2 sm:space-y-4">
<li className="flex flex-col">
<Support.SubTitle text="ํ ์ค๋ก ๊ฒฐ์ " />
<Tool.Button
type="button"
onClick={onPayment("tosspay")}
className="bg-blue-400 p-2 rounded-md text-white sm:text-lg font-bold hover:bg-blue-500 focus:outline-none focus:bg-blue-500 transition-colors"
text="toss"
/>
</li>
<li className="flex flex-col">
<Support.SubTitle text="์นด์นด์คํ์ด๋ก ๊ฒฐ์ " />
<Tool.Button
type="button"
onClick={onPayment("kakaopay")}
className="bg-yellow-300 p-2 rounded-md text-black sm:text-lg font-bold hover:bg-yellow-400 focus:outline-none focus:bg-yellow-400 transition-colors"
text="kakaopay"
/>
</li>
</ul>
</Support.Background>
</article>
</>
);
};
export default Payment;