๐Ÿ’ต ์›น ๊ฒฐ์ œ ๊ธฐ๋Šฅ ( by iamport )

๋ฐ•์ƒ์€ยท2022๋…„ 9์›” 11์ผ
0

๐Ÿงบ bleshop ๐Ÿงบ

๋ชฉ๋ก ๋ณด๊ธฐ
7/10

next.js์—์„œ recoil๊ณผ iamport๋ฅผ ์ด์šฉํ•œ ํ…Œ์ŠคํŠธ ๊ฒฐ์ œ ๊ธฐ๋Šฅ ๊ตฌํ˜„์ž…๋‹ˆ๋‹ค.

iamport์— ๋Œ€ํ•œ ๋‚ด์šฉ์€ ๊ณต์‹ ํŽ˜์ด์ง€์— ๋“ค์–ด๊ฐ€๋ฉด ํ•œ๊ตญ์–ด๋กœ ๊ฐ ์ƒํ™ฉ์— ๋งž๋Š” ๋ฉ”๋‰ด์–ผ์ด ์žˆ์œผ๋‹ˆ ์ฐธ๊ณ ํ•˜์‹œ๋ฉด ๋ฉ๋‹ˆ๋‹ค.

iamport์˜ ํƒ€์ž…์— ๋Œ€ํ•œ ์ฝ”๋“œ๋Š” ์—ฌ๊ธฐ(velog ํฌ์ŠคํŠธ)๋ฅผ ์ฐธ๊ณ ํ–ˆ์Šต๋‹ˆ๋‹ค.

๋™์ž‘ ๋ฐฉ์‹

  1. ๋กœ๊ทธ์ธํ•œ ์œ ์ €๊ฐ€ ์ƒํ’ˆ ๊ตฌ๋งค ๋ฒ„ํŠผ ํด๋ฆญ
  2. ํ•ด๋‹น ์œ ์ €๊ฐ€ ๋“ฑ๋กํ•œ ๋ฐฐ์†ก์ง€์ค‘์— ํ•˜๋‚˜ ์„ ํƒ
  3. recoil์— ๊ตฌ๋งค์— ํ•„์š”ํ•œ ์ •๋ณด๋“ค ๋“ฑ๋ก ( pg, amount, buyer_tel ๋“ฑ ๋‹จ, ์—ฌ๋Ÿฌ ์ƒํ’ˆ์„ ํ•œ๋ฒˆ์— ๊ฒฐ์ œํ•  ์ˆ˜ ์žˆ์œผ๋‹ˆ ๊ณตํ†ต ์ •๋ณด(orderData)์™€ ๊ฐœ๋ณ„ ์ •๋ณด(singleData)๋ฅผ ๋ถ„๋ฆฌํ•ด์„œ ์ €์žฅํ•จ )
  4. ๊ฒฐ์ œ ํŽ˜์ด์ง€๋กœ ์ด๋™ ( ๊ฒฐ์ œ ๋ฐฉ๋ฒ• ์„ ํƒ... ํ˜„์žฌ๋Š” kakaopay or toss )
  5. ๊ฒฐ์ œ์— ํ•„์š”ํ•œ ์ •๋ณด์™€ ํ•จ๊ป˜ iamport์— ๊ฒฐ์ œ ์š”์ฒญ
  6. ์„ ํƒํ•œ ๊ฒฐ์ œ ๋ฐฉ๋ฒ•๋Œ€๋กœ ๊ฒฐ์ œ ( ์‹ค์ œ ๊ฒฐ์ œ๊ฐ€ ์•„๋‹Œ ํ…Œ์ŠคํŠธ์šฉ ๊ฒฐ์ œ )
  7. ๊ฒฐ์ œ๊ฐ€ ์„ฑ๊ณตํ•˜๋ฉด ๊ฒฐ๊ด๊ฐ’์œผ๋กœ DB ์ปฌ๋Ÿผ ์ถ”๊ฐ€
  8. ๊ฒฐ์ œ ๊ฒฐ๊ณผ ํ…Œ์ด๋ธ”์„ ์ด์šฉํ•ด์„œ ๊ตฌ๋งค ๋ชฉ๋ก ํŽ˜์ด์ง€ ๊ตฌ์„ฑ

์ฝ”๋“œ

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";

// api
import apiService from "@src/api";

// state
import stateService from "@src/states";

// component
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";

// type
import type { Iamport } from "@src/types";
import { AxiosError } from "axios";

declare global {
  interface Window {
    IMP: Iamport;
  }
}

const Payment = () => {
  const router = useRouter();

  // 2022/09/04 - iamport ์‚ฌ์šฉ์„ ์œ„ํ•œ cdn - by 1-blue
  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);
    };
  }, []);

  // 2022/09/04 - ๊ฒฐ์ œ๋ฅผ ์œ„ํ•œ ๋ฐ์ดํ„ฐ - by 1-blue
  const paymentData = useRecoilValue(stateService.paymentService.productToPayment);

  // 2022/09/04 - iamport๋ฅผ ์ด์šฉํ•œ ๊ฒฐ์ œ - by 1-blue
  const onPayment = useCallback(
    (pg: "kakaopay" | "tosspay") => () => {
      if (!process.env.NEXT_PUBLIC_IAMPORT_CODE)
        return toast.error("iamport์˜ ๊ฐ€๋งน์  ์‹๋ณ„์ฝ”๋“œ๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค.");
      if (!paymentData) {
        toast.error(
          "๊ฒฐ์ œํ•  ์ƒํ’ˆ์˜ ์ •๋ณด๊ฐ€ ์กด์žฌํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. ๋ฉ”์ธ ํ™”๋ฉด์œผ๋กœ ์ด๋™๋ฉ๋‹ˆ๋‹ค."
        );
        return router.push("/");
      }

      // iamport๋ฅผ ์‚ฌ์šฉํ•˜๊ธฐ ์œ„ํ•ด ๊ฐ€๋งน์  ์‹๋ณ„์ฝ”๋“œ ๋“ฑ๋ก
      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 {
              // >>> ๊ฒฐ์ œ ์™„๋ฃŒ DB ์ €์žฅ ( ์‹ค์ œ ๊ฒฐ์ œ๋ผ๋ฉด ์œ ํšจ์„ฑ ๊ฒ€์‚ฌ๋ฅผ ์‹ค์‹œํ•˜๊ณ  DB์— ์ €์žฅํ•ด์•ผํ•จ )
              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;

0๊ฐœ์˜ ๋Œ“๊ธ€