티스토리 뷰

반응형

stripe

Stripe의 탄생 원인은 불평한 올라인 결제 시스템을 변경하기 위함에 있다. Pay-Pal과 비교를 통해 특징을 조금 더 살펴보자.  PayPal과 Stripe은 오늘날의 회사에서 사용할 수있는 결제 처리 옵션 중에 가장 강력한 두 가지 옵션이라고 할 수 있다. 2010년 당시에는 PayPal은 혁신적인 온라인 결제이긴 했지만, 서비스 연동을 위해서는 최대 9단계까지 거쳐야하는 어려움이 있었다. stripe는 9단계를 3단계를 줄인 결제시스템으로 10년이 넘는 기간동안 빠른 성장을 이루었다고 할 수 있다. 그리고 최근 saas가 유행하면서 간편한 결제를 위해 stripe를 많이 찾기도 하고, 해외 결제를 위해 많이 찾기도 한다. 

 

(stripe에 대한 문의가 많아, 다시 detail하게 업데이트 했다. 9월 전까지 stripe에서 가능한 3가지 구현 방법에 대한 정리와, 일반결제/구독결제 관련부분도 추가 글로 올려보려한다. 여기서는 UI custom이 많고, 일반결제와 관련된 stripe 구현에 관한 글이다. )

 

 

stripe의 특징은 아래와 같다.

  1. 어떤 서비스와도 간결한 코드로 연동이 된다.( custom이 많아질 수록 코드가 많아짐 )
  2. 결제 시 여러과정을 밝는 복잡성이 없다.
  3. 간결한 코드로 결제시스템을 만든다.

 

 온라인 결제를 구현해보고자 한다. 기본적으로 결제는 카드결제가 세계적으로 가장 많이 사용된다. 카드 결제에도 다양한 유형의 카드 결제와 프로세스 들이 존재한다. 여기서는 모든 결제를 지원하는 stripe integration을 구축하기 전에, stripe를 통한 카드결제에 대한 프로세스에 대해 먼저 알아보도록 하자.

 

stripe process

1. 카드 정보를 확인한다.

우선적으로 stripe는 카드의 formatted가 적절한지 확인을 한다. 우리가 온라인 결제를 위해 카드번호/유효기간/CVC 정보를 기입하는 부분이라고 생각하면 된다. 이 단계에서는 format만 확인할 뿐 카다 자체에 결제의 유효성은 확인 할 수 없다. 

 

2. 고객 인증을 진행한다. (Customer authentication)

이 부분은 각각의 은행해서 등록된 핸드폰번호로 인증번호를 전송해서 고객 인증을 진행하는 부분이다. 고객들은 받은 인증 번호를 등록을 하면 된다.

 

3. 권한 부여를 한다.(Authorization)

위의 인증을 진행하면, 은행은 고객의 통장에 결제를 위한 금액이 있는지를 확인한다. 확인이 된다면, 결제를 보장하기 위해 금액을 holding한다.

 

4. 결제 완료

위의 과정이 끝나면, 금액이 은행에서 결제 매체로 이동된다.

 

 

사실 위의 내용은 공식문서에 나온 개발자 기준의 flow고 사용자 기준에서는 카드번호 적고 결제를 누르면 결제가 완료된다. 이제 코드를 통해서 Web에서 Nextjs를 활용하여 Stripe 구현을 하자.

 

 

stripe coding

> 사실 stripe에는 개발자에 따라 크게 3가지 방법으로 구현이 가능하다. 아래의 방법은 custom이 많은 방법이다. 다른 방법도 추후 적어볼 예정이다. 그리고 아래는 일반결제이지만, 정기적인 결제가 가능한 구독 결제도 가능하다. 관련내용도 다른 글에서 적어보겠다.

1. Set up the server

1-1. Install Stripe Libraries

Stripe libraries를 설치하자. 아래의 명령어로 nodejs에서 설치를 하고, package.json에 stripe가 추가됐는지를 확인하자.

 

$ npm install --save stripe @stripe/stripe-js next

 

기존프로젝트가 있다면, 당연한 말이지만, 아래와 같이 stripe만 설치하면 된다.

 

$ npm install stripe @stripe/stripe-js

 

혹여나 nextjs에 대해 궁금한 점이 있다면, 아래에서 참고하자. 사실 react가 처음이라면, 아래의 내용에 대한 이해가 쉽지는 않을 것이다. nextjs 가 아닌 다른 tool을 활용한다면, 간단히 flow만 익히더라도 도움이 될 것이다.

 

[Nextjs] 프로젝트 일단 만들면서 시작하기

[Nextjs] next.config.js 기초 정리

[Nextjs] 실무 개발 환경/배포 환경 설정(.env)

[Nextjs] 데이터 가져오기 기초(SSR, SSG, CSR)

 

 

 

pages/api/create-payment-intent.js 구성하기처음 코드를 치는 부분은 Nextjs에서 웹서버를 담당하고 있는 pages/api 부분의 코드를 우선적으로 작성하자.

// This is your test secret API key.
const stripe = require("stripe")(process.env.STRIPE_SECRET_KEY);

const calculateOrderAmount = (items) => {
  // Replace this constant with a calculation of the order's amount
  // Calculate the order total on the server to prevent
  // people from directly manipulating the amount on the client
  return 1400;
};

export default async function handler(req, res) {
  const { items } = req.body;

  // Create a PaymentIntent with the order amount and currency
  const paymentIntent = await stripe.paymentIntents.create({
    amount: calculateOrderAmount(items),
    currency: "eur",
    automatic_payment_methods: {
      enabled: true,
    },
  });

  res.send({
    clientSecret: paymentIntent.client_secret,
  });
};

 

handler 부분이 SSR 부분으로 server에서 진행되는 부분이다. 아래 브라우저 client 코드에서 위의 부분으로 호출을 하면 동작을 할 것이다. req.body 부분을 보니, 호출 시 items를 담아서 보내주는 것을 알 수 있다.

 

 

stripe 관련 코드의 설명은 아래와 같다.

 

 

1-2. Create a PaymentIntent

  // Create a PaymentIntent with the order amount and currency
  const paymentIntent = await stripe.paymentIntents.create({
    amount: calculateOrderAmount(items),
    currency: "eur",
    automatic_payment_methods: {
      enabled: true,
    },
  });

  res.send({
    clientSecret: paymentIntent.client_secret,
  });

 

paymentIntent의 역할은 고객의 결제 lifecycle을 추적하는 역할을 한다. 만약 결제 실패를 추적할 뿐만아니라, 결제 자체도 한번만 가능하게 한다. 즉 paymentIntent의 핵심은, 결제완료 됐는지 확인하는 것이다. 결제할 금액(amount는 달러 기준 곱하기 100하여 넣는다.)과 화페(달러)를 넣어준다.

 

The PatmentIntent Object 관련 주소

https://stripe.com/docs/api/payment_intents/object#payment_intent_object-client_secret

 

client secret 이란

  • frontend 단 에서 결제를 완료할 수 있도록, publishable key(게시가능한 키)로 발급되어 진것이다.
  • 기본적으로 고객 외에는 저장, 기록, 노출되어서는 안된다.

 

 

 

1-3. 결제 수단 구성하기 ( Configure payment methods)

위의 코드에서 아래의 부분에 대한 설명이다.

 

    automatic_payment_methods: {
      enabled: true,
    },

 

automatic_payment_methods를 활성화 하면, 카드 및 일반적인 결제를 활성화한다. 그리고 자동적으로 브라우저로 결제하는 위치에 따라(한국 or 미국)  가장 적합한 결제 방법을 우선시 해준다.

 

 

2. Client 결제 페이지 구축( Build a checkout page on the client )

기본적으로 결제의 세부정보가 우리 서버에 전달되지 않고 다이렉트로 Stripe로 갈 수 있도록 해서 PCI 규격을 유지해야한다. 이를 위해 React app에 아래의 Stripe를 설치를 통해 추가하자. 쉽게 말하면, 카드 정보를 입력하는 form은 stripe에서 관리해서 개발자 탈취를 방지한다.

 

$ npm install --save @stripe/react-stripe-js @stripe/stripe-js

 

2-0. pages/index.js 에 코드 추가

아래의 코드를 우선적으로 추가하고, 코드 설명을 진행해 봅시다.

 

import React from "react";
import { loadStripe } from "@stripe/stripe-js";
import { Elements } from "@stripe/react-stripe-js";

import CheckoutForm from "../components/CheckoutForm";

// Make sure to call loadStripe outside of a component’s render to avoid
// recreating the Stripe object on every render.
// This is your test publishable API key.
const stripePromise = loadStripe(process.env.NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY);

export default function App() {
  const [clientSecret, setClientSecret] = React.useState("");

  React.useEffect(() => {
    // Create PaymentIntent as soon as the page loads
    fetch("/api/create-payment-intent", {
      method: "POST",
      headers: { "Content-Type": "application/json" },
      body: JSON.stringify({ items: [{ id: "xl-tshirt" }] }),
    })
      .then((res) => res.json())
      .then((data) => setClientSecret(data.clientSecret));
  }, []);

  const appearance = {
    theme: 'stripe',
  };
  const options = {
    clientSecret,
    appearance,
  };

  return (
    <div className="App">
      {clientSecret && (
        <Elements options={options} stripe={stripePromise}>
          <CheckoutForm />
        </Elements>
      )}
    </div>
  );
}

 

next 관련된 이야기를 간단히 하면, 위의 코드는 브라우저인 frontend 부분의 코드이다. nextjs가 frontend인데 무슨이야기 인가요??? 라는 질문을 한다면, nextjs의 CSR, SSR에 대한 구글링을 하고 오자.

 

 

2-1.  Load Stripe.js

loadStripe()는 사용가능한 API키를 통해서 Stripe 라이브러리를 구성한다. 주의할 점은 Stripe object를 중복호출을 방지하기 위해서 component 외부에서 호출을 해야한다. 주의 할점은 우리가 key를 발급 받을 때 퍼블릭키와 시크릿키를 두개를 받는데, 여기서는 NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY 퍼블릭 키임을 인지하자.

 

// This is your test publishable API key.
const stripePromise = loadStripe(process.env.NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY);

 

2-2. Fetch a PaymentIntent

pages/api/create-payment-intent.js 부분(처음 코드 작성한 부분)을 호출해서 PaymentIntent의 clientSecret을 가지고 오자. 코드를 보면, 화면 UI 페이지를 추후 CheckoutForm.jsx를 만들어서 pages/index.js에 추가할 예정인것들 확인 할 수 있다. 이 페이지가 로딩되는 즉시 PaymentIntent를 생성하기 위해 useEffect를 사용( react 기초 )해서 서버의 엔드포인트로 요청을 날린다. 그 후에 setClientSecret으로 데이터가 들어가게된다. 그러면 그러면 clientSecret에 들어간 데이터를 통해 결제를 진행하면 된다.

 

  React.useEffect(() => {
    // Create PaymentIntent as soon as the page loads
    fetch("/api/create-payment-intent", {
      method: "POST",
      headers: { "Content-Type": "application/json" },
      body: JSON.stringify({ items: [{ id: "xl-tshirt" }] }),
    })
      .then((res) => res.json())
      .then((data) => setClientSecret(data.clientSecret));
  }, []);

 

2-3. Initialize Stripe Elements

UI 컴포넌트를 포함시키자. 아래의 코드를 보면,  clientSecret를 options에 담아서 Elemnets provider로 전달을 한다. 그리고 loadStripe의 프로미스된 결과를 전달하여, Stripe 서비스에 접근 할 수 있도록 한다고 이해하면 된다.

 

  return (
    <div className="App">
      {clientSecret && (
        <Elements options={options} stripe={stripePromise}>
          <CheckoutForm />
        </Elements>
      )}
    </div>
  );

 

추가적으로 options에 포함된, appearance는 아래에서 볼 카드정보 입력란의 UI부분 settings이기 때문에 logic에는 큰 상관은 없다.

 

3. From 만들기

3-0. CheckoutForm.jsx 만들기

아래의 코드를 원하는 위치에 만든다. 여기서 <CheckoutForm /> 컴포넌트는 위의 코드에서 확인 할 수 있듯, 반드시 <Elements></Elements> 내부에 포함되어 있어야 한다. 우선 전체 코드를 보고, 세부 코드 확인을 해보자.

 

import React from "react";
import {
  PaymentElement,
  useStripe,
  useElements
} from "@stripe/react-stripe-js";

export default function CheckoutForm() {
  const stripe = useStripe();
  const elements = useElements();

  const [email, setEmail] = React.useState('');
  const [message, setMessage] = React.useState(null);
  const [isLoading, setIsLoading] = React.useState(false);

  React.useEffect(() => {
    if (!stripe) {
      return;
    }

    const clientSecret = new URLSearchParams(window.location.search).get(
      "payment_intent_client_secret"
    );

    if (!clientSecret) {
      return;
    }

    stripe.retrievePaymentIntent(clientSecret).then(({ paymentIntent }) => {
      switch (paymentIntent.status) {
        case "succeeded":
          setMessage("Payment succeeded!");
          break;
        case "processing":
          setMessage("Your payment is processing.");
          break;
        case "requires_payment_method":
          setMessage("Your payment was not successful, please try again.");
          break;
        default:
          setMessage("Something went wrong.");
          break;
      }
    });
  }, [stripe]);

  const handleSubmit = async (e) => {
    e.preventDefault();

    if (!stripe || !elements) {
      // Stripe.js has not yet loaded.
      // Make sure to disable form submission until Stripe.js has loaded.
      return;
    }

    setIsLoading(true);

    const { error } = await stripe.confirmPayment({
      elements,
      confirmParams: {
        // Make sure to change this to your payment completion page
        return_url: "http://localhost:3000",
        receipt_email: email,
      },
    });

    // This point will only be reached if there is an immediate error when
    // confirming the payment. Otherwise, your customer will be redirected to
    // your `return_url`. For some payment methods like iDEAL, your customer will
    // be redirected to an intermediate site first to authorize the payment, then
    // redirected to the `return_url`.
    if (error.type === "card_error" || error.type === "validation_error") {
      setMessage(error.message);
    } else {
      setMessage("An unexpected error occurred.");
    }

    setIsLoading(false);
  };

  return (
    <form id="payment-form" onSubmit={handleSubmit}>
      <input
        id="email"
        type="text"
        value={email}
        onChange={(e) => setEmail(e.target.value)}
        placeholder="Enter email address"
      />
      <PaymentElement id="payment-element" />
      <button disabled={isLoading || !stripe || !elements} id="submit">
        <span id="button-text">
          {isLoading ? <div className="spinner" id="spinner"></div> : "Pay now"}
        </span>
      </button>
      {/* Show any error or success messages */}
      {message && <div id="payment-message">{message}</div>}
    </form>
  );
}

 

처음 위 코드를 보면 이해가 안되는 것이 카드 번호 적는 input 태그가 없다. <PaymentElement id="payment-element" /> 부분이 카드번호를 적는 컴포넌트다. 관련 된 것은 모두 stripe에서 관리하고, 오류처리도 나옴을 인지하고 아래의 코드 설명을 확인하자.

 

3-1. Set up the state

우선적으로 로직에 필요한 상태 값들을 선언한다. 결제 추적, 오류 표시, 사용자 인터페이스 관리를 위한 상태 초기화 정도로 생각하면 좋을 것 같다.

 

  const [message, setMessage] = React.useState(null);
  const [isLoading, setIsLoading] = React.useState(false);

 

3-2.  Store a reference to Stripe

useStripe()와 useElements() 훅을 사용하여 Stripe librarty에 접근할 수 있다. 만약 함수형 컴포넌트가 아닌 class를 사용중이라면 ElementsConsumer를 찾아서 사용하면 된다.

 

import {
  PaymentElement,
  useStripe,
  useElements
} from "@stripe/react-stripe-js";

export default function CheckoutForm() {
  const stripe = useStripe();
  const elements = useElements();
  
  ...

 

3-3. Add the PaymentElement

 

@stripe/react-stripe-js 에서 거저온 PaymentElement를 추가한다.

 

import {
  PaymentElement,
  useStripe,
  useElements
} from "@stripe/react-stripe-js";

 

아래와 같이 return 내부에 추가를 해준다. 이는 다양한 결제 방식을 위한 결제 세부사항을 포함하는 dynamic form 이다.  iframe으로 되어 있고, 사용자가 결제 방식 유형을 선택할 수 있도록 한다. 선택한 유형에 따라 자동으로 필수적인 결제 세부사항들을 수집한다.

 

return (
    <form id="payment-form" onSubmit={handleSubmit}>
      
      ...
      
      <PaymentElement id="payment-element" />
      
      ...

 

3-4. Optional: style the Payment Element

소제목의 이름과 같이 style 부분이다. 

 

  const appearance = {
    theme: 'stripe',
  };

 

theme에 현재는 stripe라는 옵션이 적혀있다. stripe 외에도 night나 flat를 적어도 다른 style 옵션이 적용되긴하는데 굳이 변경할 필요는 없어보인다. 세부 디테일한 변경 부분은 아래의 uri를 참고하자.

https://stripe.com/docs/elements/appearance-api

 

Elements Appearance API

Customize the look and feel of Elements to match the design of your site.

stripe.com

 

 

3-5. Complete the patment

값을 입력한 후에 결제 버튼을 누르면 PaymentElement에서 confirmPayment()를 호출한다. 이때 아래와 같이 포함된 return_url은 결제가 완료한 후에 redirect할 uri를 함께 보내준다. 이때 추가 인증이 필요한 결제의 경우는 결제 이전에 모달을 자동으로 띄워주거나, 인증 페이지로 redirects 된다. 

 

  const handleSubmit = async (e) => {
    e.preventDefault();

    ...

    const { error } = await stripe.confirmPayment({
      elements,
      confirmParams: {
        // Make sure to change this to your payment completion page
        return_url: "http://localhost:3000",
      },
    });
    
    ...

 

성공적으로 결제가 마무리 된다면, redirect되어 페이지가 이동된다. 결제가 실패할 결우는 아래를 보자.

 

 

3-6. Handle errors

위의 요청에서 카드가 거절된다면, Stripe.js는 error를  리턴한다. error 메세지를 사용자에게 보여주고 재시도를 하게 하면 된다.

 

  const handleSubmit = async (e) => {
   
   ...
   
    if (error.type === "card_error" || error.type === "validation_error") {
      setMessage(error.message);
    } else {
      setMessage("An unexpected error occurred.");
    }

    setIsLoading(false);
  };

 

 

3-7. Show a payment status message

결제 완료 후에 return_url로 리다이렉션을 할 때, payment_intent_client_secret라는 쿼리 매개변수가 추가된다. 이를 통해 PaymentIntent를 검색하여 아래와 같이 상황을 handling 할 수 있다. 예를 들어, 확인 이메일 보내기, DB에 판매 기록, 배송 worklow 같은 작업을 시작하는 것이 가능하다.

 

  React.useEffect(() => {
    if (!stripe) {
      return;
    }

    const clientSecret = new URLSearchParams(window.location.search).get(
      "payment_intent_client_secret"
    );

    if (!clientSecret) {
      return;
    }

    stripe.retrievePaymentIntent(clientSecret).then(({ paymentIntent }) => {
      switch (paymentIntent.status) {
        case "succeeded":
          setMessage("Payment succeeded!");
          break;
        case "processing":
          setMessage("Your payment is processing.");
          break;
        case "requires_payment_method":
          setMessage("Your payment was not successful, please try again.");
          break;
        default:
          setMessage("Something went wrong.");
          break;
      }
    });
  }, [stripe]);

 

간단히 nextjs에 대한 설명을 하면, 위 코드의 useEffect 부분은 stripe의 변경을 있을 때 마다 실행하는 것을 확인 할 있다.

 

이러한 결과가 나온다면, 우리의 목표는 달성한 것이다. 사실 여기까지만 하면, 여차여차 결제는 완료이다. 끝이다. 보안, 관리, 카드 유효성 검사 등등은 stripe가 알아서 다 해준다. 아래 부터는 추가적인 심화이기 때문에, 필요한 부분만 확인하면 된다. 

 


 

위의 웹 처리는 아래의 Dashboard webhook tool 이나 Webhook guide를 참고해서 진행하면된다.

Dashboard webhook tool 

> https://dashboard.stripe.com/login?redirect=%2Fwebhooks   (로그인 필요)

Webhook guide

> https://stripe.com/docs/webhooks/quickstart

 

가상의 card를 통해 실행하는 것과 같은 테스트하는 방법은 아래와 같다. 

https://stripe.com/docs/payments/accept-a-payment?platform=web&ui=elements#web-test-the-integration 

 

Accept a payment

Securely accept payments online.

stripe.com

 

 

3-8. Send an email receipt

Stripe는 우리 brand logo를 포함한 email 영수증을 사용자에게 보내는 것도 가능하다. 디테일한 설정은 아래의 uri에서 한다.

https://dashboard.stripe.com/login?redirect=%2Fsettings%2Fbranding 

 

 

import React from "react";
import {
  PaymentElement,
  useStripe,
  useElements
} from "@stripe/react-stripe-js";

export default function CheckoutForm() {
  const stripe = useStripe();
  const elements = useElements();

  const [email, setEmail] = React.useState('');
  const [message, setMessage] = React.useState(null);
  const [isLoading, setIsLoading] = React.useState(false);

  React.useEffect(() => {
    if (!stripe) {
      return;
    }

    const clientSecret = new URLSearchParams(window.location.search).get(
      "payment_intent_client_secret"
    );

    if (!clientSecret) {
      return;
    }

    stripe.retrievePaymentIntent(clientSecret).then(({ paymentIntent }) => {
      switch (paymentIntent.status) {
        case "succeeded":
          setMessage("Payment succeeded!");
          break;
        case "processing":
          setMessage("Your payment is processing.");
          break;
        case "requires_payment_method":
          setMessage("Your payment was not successful, please try again.");
          break;
        default:
          setMessage("Something went wrong.");
          break;
      }
    });
  }, [stripe]);

  const handleSubmit = async (e) => {
    e.preventDefault();

    if (!stripe || !elements) {
      // Stripe.js has not yet loaded.
      // Make sure to disable form submission until Stripe.js has loaded.
      return;
    }

    setIsLoading(true);

    const { error } = await stripe.confirmPayment({
      elements,
      confirmParams: {
        // Make sure to change this to your payment completion page
        return_url: "http://localhost:3000",
        receipt_email: email,
      },
    });

    // This point will only be reached if there is an immediate error when
    // confirming the payment. Otherwise, your customer will be redirected to
    // your `return_url`. For some payment methods like iDEAL, your customer will
    // be redirected to an intermediate site first to authorize the payment, then
    // redirected to the `return_url`.
    if (error.type === "card_error" || error.type === "validation_error") {
      setMessage(error.message);
    } else {
      setMessage("An unexpected error occurred.");
    }

    setIsLoading(false);
  };

  return (
    <form id="payment-form" onSubmit={handleSubmit}>
      <input
        id="email"
        type="text"
        value={email}
        onChange={(e) => setEmail(e.target.value)}
        placeholder="Enter email address"
      />
      <PaymentElement id="payment-element" />
      <button disabled={isLoading || !stripe || !elements} id="submit">
        <span id="button-text">
          {isLoading ? <div className="spinner" id="spinner"></div> : "Pay now"}
        </span>
      </button>
      {/* Show any error or success messages */}
      {message && <div id="payment-message">{message}</div>}
    </form>
  );
}

 

 

아래의 추가된 코드는 useState를 활용한 email 상태를 추적하는 코드와, email을 적을 수 있는 input 태그를 추가했다. 

 

export default function CheckoutForm() {
  ...
  
  const [email, setEmail] = React.useState('');

  ...
  
  
  return (
    <form id="payment-form" onSubmit={handleSubmit}>
      <input
        id="email"
        type="text"
        value={email}
        onChange={(e) => setEmail(e.target.value)}
        placeholder="Enter email address"
      />
      
      ...

 

그리고 3-5에서 설정한, confirmPayment 부분에 receipt_email: email 부분을 아래와 같이 추가해 준다.

 

    const { error } = await stripe.confirmPayment({
      elements,
      confirmParams: {
        // Make sure to change this to your payment completion page
        return_url: "http://localhost:3000",
        receipt_email: email,
      },
    });

 

단, test 모드에서는 실제로는 보내지 않는다. 실 서비스에서만 영수증이 전달되니 참고하자.

 

3-9. Save payment details after payment

이 부분은 반복고객이 있는 SaaS나 e-commerce에서 주로 사용한다. 쉽게 말하면, 결제 정보를 저장하는 부분이라고 생각하면 된다.

 

pages/api/create-payment-intent.js 부분에 다음 값들을 추가하면 아래와 같다.

 

// This is your test secret API key.
const stripe = require("stripe")(process.env.STRIPE_SECRET_KEY);

const calculateOrderAmount = (items) => {
  // Replace this constant with a calculation of the order's amount
  // Calculate the order total on the server to prevent
  // people from directly manipulating the amount on the client
  return 1400;
};

const chargeCustomer = async (customerId) => {
  // Lookup the payment methods available for the customer
  const paymentMethods = await stripe.paymentMethods.list({
    customer: customerId,
    type: "card",
  });
  try {
    // Charge the customer and payment method immediately
    const paymentIntent = await stripe.paymentIntents.create({
      amount: 1099,
      currency: "eur",
      customer: customerId,
      payment_method: paymentMethods.data[0].id,
      off_session: true,
      confirm: true,
    });
  } catch (err) {
    // Error code will be authentication_required if authentication is needed
    console.log("Error code is: ", err.code);
    const paymentIntentRetrieved = await stripe.paymentIntents.retrieve(err.raw.payment_intent.id);
    console.log("PI retrieved: ", paymentIntentRetrieved.id);
  }
};

export default async function handler(req, res) {
  const { items } = req.body;
  // Alternatively, set up a webhook to listen for the payment_intent.succeeded event
  // and attach the PaymentMethod to a new Customer
  const customer = await stripe.customers.create();

  // Create a PaymentIntent with the order amount and currency
  const paymentIntent = await stripe.paymentIntents.create({
    customer: customer.id,
    setup_future_usage: "off_session",
    amount: calculateOrderAmount(items),
    currency: "eur",
    automatic_payment_methods: {
      enabled: true,
    },
  });

  res.send({
    clientSecret: paymentIntent.client_secret,
  });
};

 

create a customer

아래와 같이 카드정보는 Customer object에 저장이 된다.  PaymentIntent를 생성하기 전에 새로운 Customer object를 만들어주면 된다. 여기에 우리는 이름, 이메일, 배달주소 등과 같은 여러 정보를 저장 할 수 있다.

const customer = await stripe.customers.create();

 

 

Add the customer to the PaymentIntent

아래의 속성 중에 customer과 setup_futere_usage를 추가해 주자. 고객 ID는 는 customer에 넣어준 것이고, setup_future_usage는 지불방법을 어떻게 사용할 것인지 설정 한 것이다. (유럽, 인도 와 같은 특정 국가에서는 지불 세부 정보 재사용에 대한 요구 사항이 포항되어 있을 수 있다.) 기본적으로는 off_session으로 설정을하면되고, 최적화는 위해서는 아래의 url를 참고하여 읽어보자.

https://stripe.com/docs/payments/payment-intents#future-usage

 

The Payment Intents API

Learn how to use the Payment Intents API for Stripe payments.

stripe.com

지원하는 지불 방법에 대한 목록은 아래와 같다.

https://stripe.com/docs/payments/payment-methods/integration-options#additional-api-supportability

 

Payment method integration options

Learn about the different ways to integrate payment methods.

stripe.com

 

결제가 완료 된다면, Stripe은 자동으로 결제 세부정도를 고객에게 첨부한다.

 

  // Create a PaymentIntent with the order amount and currency
  const paymentIntent = await stripe.paymentIntents.create({
    customer: customer.id,
    setup_future_usage: "off_session",
    amount: calculateOrderAmount(items),
    currency: "eur",
    automatic_payment_methods: {
      enabled: true,
    },
  });

 

 

Charge the saved PaymentMethod

paymentMethod를 다시 청구 할 때, 새로운 PaymentIntent를 Customer ID로  만들고, off_session과 confirm을 true로 설정을 해주면 된다.

const chargeCustomer = async (customerId) => {
  // Lookup the payment methods available for the customer
  const paymentMethods = await stripe.paymentMethods.list({
    customer: customerId,
    type: "card",
  });
  try {
    // Charge the customer and payment method immediately
    const paymentIntent = await stripe.paymentIntents.create({
      amount: 1099,
      currency: "eur",
      customer: customerId,
      payment_method: paymentMethods.data[0].id,
      off_session: true,
      confirm: true,
    });
  } catch (err) {
    // Error code will be authentication_required if authentication is needed
    console.log("Error code is: ", err.code);
    const paymentIntentRetrieved = await stripe.paymentIntents.retrieve(err.raw.payment_intent.id);
    console.log("PI retrieved: ", paymentIntentRetrieved.id);
  }
};

 

 

추후 추가 정리를 할 부분은 아래와 같다.

  1. 우리 회사 통장으로 옮기는 코드
  2. 환불하는 코드
  3. 결제 성공 후에 주문을 이행하는 webhook 설정 : 결제가 완료 된 이후에 email보내기, DB 업데이트 와 같은 event 를 등록
반응형
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2024/04   »
1 2 3 4 5 6
7 8 9 10 11 12 13
14 15 16 17 18 19 20
21 22 23 24 25 26 27
28 29 30
글 보관함