결제 폼 만들기

이제 설정 페이지에 사용자의 신용카드 정보를 입력받고, 스트라이프 토큰을 얻은 후 결제 API를 호출하는 폼을 추가할 것입니다. 먼저 Stripe React SDK를 프로젝트에 추가해 보겠습니다.

Change indicator packages/frontend/ 디렉토리에서 다음 명령어를 실행하세요.

$ npm install @stripe/react-stripe-js

다음으로 결제 폼 컴포넌트를 만들어 보겠습니다.

Change indicator src/components/BillingForm.tsx 파일에 다음 코드를 추가하세요.

import React, { useState } from "react";
import Form from "react-bootstrap/Form";
import Stack from "react-bootstrap/Stack";
import { useFormFields } from "../lib/hooksLib";
import { Token, StripeError } from "@stripe/stripe-js";
import LoaderButton from "../components/LoaderButton";
import { CardElement, useStripe, useElements } from "@stripe/react-stripe-js";
import "./BillingForm.css";

export interface BillingFormType {
  isLoading: boolean;
  onSubmit: (
    storage: string,
    info: { token?: Token; error?: StripeError }
  ) => Promise<void>;
}

export function BillingForm({ isLoading, onSubmit }: BillingFormType) {
  const stripe = useStripe();
  const elements = useElements();
  const [fields, handleFieldChange] = useFormFields({
    name: "",
    storage: "",
  });
  const [isProcessing, setIsProcessing] = useState(false);
  const [isCardComplete, setIsCardComplete] = useState(false);

  isLoading = isProcessing || isLoading;

  function validateForm() {
    return (
      stripe &&
      elements &&
      fields.name !== "" &&
      fields.storage !== "" &&
      isCardComplete
    );
  }

  async function handleSubmitClick(event: React.FormEvent<HTMLFormElement>) {
    event.preventDefault();

    if (!stripe || !elements) {
      // Stripe.js가 아직 로드되지 않았습니다. Stripe.js가 로드될 때까지 폼 제출을 비활성화하세요.
      return;
    }

    if (!elements.getElement(CardElement)) {
      return;
    }

    setIsProcessing(true);

    const cardElement = elements.getElement(CardElement);

    if (!cardElement) {
      return;
    }

    const { token, error } = await stripe.createToken(cardElement);

    setIsProcessing(false);

    onSubmit(fields.storage, { token, error });
  }

  return (
    <Form className="BillingForm" onSubmit={handleSubmitClick}>
      <Form.Group controlId="storage">
        <Form.Label>Storage</Form.Label>
        <Form.Control
          min="0"
          size="lg"
          type="number"
          value={fields.storage}
          onChange={handleFieldChange}
          placeholder="Number of notes to store"
        />
      </Form.Group>
      <hr />
      <Stack gap={3}>
        <Form.Group controlId="name">
          <Form.Label>Cardholder&apos;s name</Form.Label>
          <Form.Control
            size="lg"
            type="text"
            value={fields.name}
            onChange={handleFieldChange}
            placeholder="Name on the card"
          />
        </Form.Group>
        <div>
          <Form.Label>Credit Card Info</Form.Label>
          <CardElement
            className="card-field"
            onChange={(e) => setIsCardComplete(e.complete)}
            options={{
              style: {
                base: {
                  fontSize: "16px",
                  fontWeight: "400",
                  color: "#495057",
                  fontFamily: "'Open Sans', sans-serif",
                },
              },
            }}
          />
        </div>
        <LoaderButton
          size="lg"
          type="submit"
          isLoading={isLoading}
          disabled={!validateForm()}
        >
          Purchase
        </LoaderButton>
      </Stack>
    </Form>
  );
}

여기서 어떤 작업을 하는지 간단히 살펴보겠습니다:

  • 먼저 useStripe를 호출하여 Stripe 객체에 대한 참조를 얻습니다.

  • 폼 필드에는 사용자가 저장할 노트의 수를 입력할 수 있는 number 타입의 입력 필드와 신용카드에 적힌 이름을 입력받는 필드가 있습니다. 이 값들은 커스텀 React Hook인 useFormFields에서 제공하는 handleFieldChange 메서드를 통해 상태로 관리됩니다.

  • 신용카드 번호 입력 필드는 Stripe React SDK에서 제공하는 CardElement 컴포넌트를 사용합니다.

  • 제출 버튼은 Stripe 토큰을 요청하거나 결제 API를 호출할 때 로딩 상태가 됩니다. 하지만 설정 컨테이너에서 결제 API를 호출하기 때문에 props.isLoading을 사용하여 버튼의 상태를 설정합니다.

  • 폼 유효성 검사는 이름, 노트 수, 카드 정보가 모두 입력되었는지 확인합니다. 카드 정보는 CardElementonChange 메서드를 사용하여 검사합니다.

  • 마지막으로 사용자가 폼을 완료하고 제출하면 CardElement를 전달하여 Stripe를 호출합니다. 이를 통해 특정 호출에 대한 토큰을 생성합니다. 이 토큰과 저장할 노트 수를 onSubmit 메서드를 통해 설정 페이지로 전달합니다. 이 부분은 곧 설정할 예정입니다.

React Stripe SDK 사용법에 대해 더 알아볼 수 있습니다.

또한 카드 필드에 스타일을 추가하여 UI와 일관성을 유지하겠습니다.

Change indicator src/components/BillingForm.css 파일을 생성하세요.

.BillingForm .card-field {
  line-height: 1.5;
  padding: 0.65rem 0.75rem;
  background-color: var(--bs-body-bg);
  border: 1px solid var(--bs-border-color);
  border-radius: var(--bs-border-radius-lg);
  transition: border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out;
}

.BillingForm .card-field.StripeElement--focus {
  outline: 0;
  border-color: #86B7FE;
  box-shadow: 0 0 0 .25rem rgba(13, 110, 253, 0.25);
}

이 스타일은 복잡해 보일 수 있지만, 페이지의 다른 폼 필드와 일관성을 유지하기 위해 해당 필드의 스타일을 복사한 것입니다.

다음으로 이 폼을 설정 페이지에 연결하겠습니다.