웹페이지에 결제 컴포넌트 만들기

본격적으로 자신의 웹사이트에서 어떤 서비스를 판매하기 위해서는

결제 기능의 삽입이 필수입니다.

이를 위해서 우선 Paypal 을  통해서 결제가 가능하도록 프론트엔드를 짜보겠습니다.

저는 React + node.js 서버로 진행하고 있습니다.

코드 정리

먼저 어떤 방식으로 각 Plan을 나타낼지 프론트엔드 코드를 작성하겠습니다. 

저는 Price Box 들이 줄줄이 나타나는 형태로 프론트엔드를 구축하기 위해, 

먼저 데이터를 정리했습니다.

				
					const priceData = [
  {
    title: "Free",
    price: "Pricing : Free",
    features: ["Free grade"],
    link: "/payment/Free",
  },
  {
    title: "Basic",
    price: "Pricing : $10 per month",
    annualPrice: "$100 per year",
    monthlyPrice: "$10 per month",

    features: ["Basic grade"],

    link: "/payment/basic",
  },
  {
    title: "Standard",
    price: "Pricing : $15 per month",

    annualPrice: "$150 per year",
    monthlyPrice: "$15 per month",
    features: ["Standard grade"],
    link: "/payment/standard",
  },
];

				
			

위와 같이 priceData 내부에 각각의 Key 값 별로 Value를 정의하였습니다.

이제 이 priceData 내부의 Key 값들을 활용하여, 프론트엔드 구축이 아래와 같이 진행 가능합니다.

				
					        <div className="price-container">
          {priceData
            .map((plan, index) => (
              <div key={index}>
                <div className="price-card">
                  <h5 className="price-title">{plan.title}</h5>
                  <p className="price">{plan.price}</p>
                  <ul className="features">
                    {plan.features.map((feature, index) => (
                      <li key={index}>{feature}</li>
                    ))}
                  </ul>
                  <button
                    className="btn-subscribe"
                    onClick={() => showPaymentModal(plan)}
                  >
                    Get Start!
                  </button>
                </div>
              </div>
            ))}
        </div>

				
			

위 내용을 짚어보면, piceData 내부에 있는 { } 을 순회하며, 그 내부에 있는 title, price, feature 을 활용하여 

Price-card 라는 div 내부에 내용이 생성됩니다.

이제 저 버튼을 누르면 동작하는 showPaymentModal(paln) 을 통해서 실제 결제가 가능한 팝업창이 등장하도록 해보겠습니다.

				
					
  return (
    <>      
      
        <div className="price-container">
          {priceData
            .map((plan, index) => (
              <div key={index}>
                <div className="price-card">
                  <h5 className="price-title">{plan.title}</h5>
                  <p className="price">{plan.price}</p>
                  <ul className="features">
                    {plan.features.map((feature, index) => (
                      <li key={index}>{feature}</li>
                    ))}
                  </ul>
                  <button
                    className="btn-subscribe"
                    onClick={() => showPaymentModal(plan)}
                  >
                    Get Start!
                  </button>
                </div>
              </div>
            ))}
        </div>
        
              {selectedPlan && (
        <PaymentModal onClose={hidePaymentModal} plan={selectedPlan} />
      )}
      
    </>
    
    )

				
			

그럼 이제 전체 코드를 차근차근 정리해보겠습니다.

1. 결제 박스가 나타나는 컴포넌트

해당 코드 내에 const priceData 를 지정해서 원하는 정보를 만들어줘야 한다는 것 잊으면 안 됩니다.

윗 부분을 참고해서 양식에 맞춰 집어넣으면 됩니다. 

				
					import React, { useState } from "react";
import "bootstrap/dist/css/bootstrap.min.css"; // 기본 부트스트랩 디자인을 사용 
import "./price.css"; // 원하는 디자인 CSS
import { Elements } from "@stripe/react-stripe-js";
import { loadStripe } from "@stripe/stripe-js";
import PaymentModal from "./PaymentModal";


const priceData = [
{},
{},
... 
];


const Price = () => {
    const [selectedPlan, setSelectedPlan] = useState(null);

    // 결제 모달 표시 함수
    const showPaymentModal = (plan) => {
      setSelectedPlan(plan);
    };
  
    // 결제 모달 숨기기 함수
    const hidePaymentModal = () => {
      setSelectedPlan(null);
    };
  
  
    return (
      <>
        <div className="price-container">
          {priceData
            .map((plan, index) => (
              <div key={index}>
                <div className="price-card">
                  <h5 className="price-title">{plan.title}</h5>
                  <p className="price">{plan.price}</p>
                  <ul className="features">
                    {plan.features.map((feature, index) => (
                      <li key={index}>{feature}</li>
                    ))}
                  </ul>
                  <button
                    className="btn-subscribe"
                    onClick={() => showPaymentModal(plan)}
                  >
                    Get Start!
                  </button>
                </div>
              </div>
            ))}
        </div>
  
        {selectedPlan && (
          <Elements stripe={stripePromise}>
            <PaymentModal onClose={hidePaymentModal} plan={selectedPlan} />
          </Elements>
        )}
      </>
    );
  };

  export default Price;
				
			

2. 결제가 가능하게 해주는 팝업창

해당 코드의 위치를 위쪽 Price.js 에서 import 해주면,

우리가 설정한 Plan 대로 선택이 가능한 결제창이 등장하게 됩니다. 

				
					import React, { useState } from "react";
import { CardElement, useStripe, useElements } from "@stripe/react-stripe-js";
import "bootstrap/dist/css/bootstrap.min.css";
import "./price.css";
import { Helmet } from "react-helmet"; // Helmet import

// PaymentModal 컴포넌트
const PaymentModal = ({ onClose, plan }) => {
  const [paymentOption, setPaymentOption] = useState("monthly");

  const stripe = useStripe();
  const elements = useElements();

  console.log("Rendering PaymentModal", { stripe, elements, plan });

  const handlePayment = async () => {
    console.log("handlePayment called");

    if (!stripe || !elements) {
      // Stripe.js가 아직 로드되지 않았다면 중단
      return;
    }

    console.log("Processing payment...");

    const cardElement = elements.getElement(CardElement);

    const { error, paymentMethod } = await stripe.createPaymentMethod({
      type: "card",
      card: cardElement,
    });

    if (error) {
      console.log("[error]", error);
    } else {
      console.log("[PaymentMethod]", paymentMethod);
      // 백엔드로 paymentMethod.id 전송
      // 결제 처리 로직 실행
    }
  };

  return (
    <div className="payment-modal-overlay">
      <div className="payment-modal-container">
        <div className="modal-header">
          <h2 className="modal-title">{plan.title}</h2>
        </div>
        <div className="modal-body">
          <div className="payment-options">
            <label className="payment-option">
              <input
                type="radio"
                name="paymentOption"
                value="monthly"
                checked={paymentOption === "monthly"}
                onChange={() => setPaymentOption("monthly")}
              />
              {plan.monthlyPrice}
            </label>
            <label className="payment-option">
              <input
                type="radio"
                name="paymentOption"
                value="annual"
                checked={paymentOption === "annual"}
                onChange={() => setPaymentOption("annual")}
              />
              {plan.annualPrice}
            </label>
          </div>
        </div>

        <CardElement />

        <div className="modal-footer">
          <button className="btn-pay" onClick={handlePayment}>
            Pay Now
          </button>
          <button className="btn-close" onClick={onClose}>
            Close
          </button>
        </div>
      </div>
    </div>
  );
};

export default PaymentModal;