1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

payjp.js v2をReact+TypeScriptで使用する方法

Last updated at Posted at 2024-10-08

前提

この記事は、payjp.jsをもともと使用していた人が、Reactでの実装方法を知りたいときに参考にすることを目的としています。payjp.jsの基本的な使い方は理解している前提です。

以下、pnpmを使用しますがnpmでもなんでも良いです。

プロジェクトの作成

まずはViteでReactのプロジェクトを作成します。

pnpm create vite

型定義のインストール

プロジェクトのディレクトリに入って、有志の作成した型定義をインストールします。

pnpm add -D typedef-payjp-js

型定義のためのglobal.d.tsの用意

型定義を使用できるように、src下にglobal.d.tsを置きます(@typesでもいいよ)。

global.d.ts
import type PayjpJs from "typedef-payjp-js";

/**
 * windowオブジェクトからpayjp.jsのAPIを参照
 */
declare global {
	interface Window {
		Payjp?: (
			publicKey: string,
			options?: { locale?: PayjpJs.Locale },
		) => PayjpJs.Payjp;
	}
}

payjp.jsのスクリプトを追加

index.htmlにpayjp.jsのスクリプトを追加します。
(試してないけど、下記mount時にdynamic importもできるのかな?)

index.html
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <link rel="icon" type="image/svg+xml" href="/vite.svg" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Vite + React + TS</title>
+    <script src="https://js.pay.jp/v2/pay.js"></script>
  </head>
  <body>
    <div id="root"></div>
    <script type="module" src="/src/main.tsx"></script>
  </body>
</html>

邪魔なCSSなどの削除

App.tsx
const App = () => {
  return (
    <div>
      <h1>Hello, React 18!</h1>
    </div>
  );
};

export default App;

わかりやすいように超シンプルな状態にしましょう。

main.ts
import { StrictMode } from 'react'
import { createRoot } from 'react-dom/client'
import App from './App.tsx'
- import './index.css'

createRoot(document.getElementById('root')!).render(
  <StrictMode>
    <App />
  </StrictMode>,
)

App.tsxでpayjpを呼べるようにする

App.tsx
+ import type PayjpJs from "typedef-payjp-js";
+ const payjp = window?.Payjp?.("pk_test_token") as unknown as PayjpJs.Payjp;

const App = () => {
  return (
    <div>
      <h1>Hello, React 18!</h1>
    </div>
  );
};

export default App;

もちろんpayjpUtil.tsみたいなファイルからexportしてもOK。
これ以後、pk_test_tokenはご自身の持つテストトークンにしてください。

カード入力欄用のdivを作成

App.tsx
import type PayjpJs from "typedef-payjp-js";
const payjp = window?.Payjp?.("pk_test_token") as unknown as PayjpJs.Payjp;

const App = () => {
  return (
+   <div style={{ maxWidth: "400px", backgroundColor: "#eee" }}>
-   <div>
-    <h1>Hello, React 18!</h1>
+    <div id="v2-demo" />
    </div>
  );
};

export default App;

ついでにちょっとだけデザインも追加。

cardElementの作成

App.tsx
import type PayjpJs from "typedef-payjp-js";
const payjp = window?.Payjp?.("pk_test_token") as unknown as PayjpJs.Payjp;

const App = () => {
+  let cardElement: PayjpJs.PayjpElement | null = null;
+  const handlePayDivMount = () => {
+    const elements = payjp.elements();
+    cardElement = elements.create("card");
+    cardElement.mount("#v2-demo");
+  };
  return (
    <div style={{ maxWidth: "400px", backgroundColor: "#eee" }}>
-     <div id="v2-demo" />
+     <div id="v2-demo" ref={handlePayDivMount} />
    </div>
  );
};

export default App;

Reactのrefは関数を渡すことができて、mountされたときに関数を作動させることができます。もともとdivが存在しているのであればuseEffectなどでも処理可能ですが、たとえばモーダルを開いたときにdivが出現するような場合、useRefとuseEffectだとうまく動作しないことがあります。そういうときはrefで関数を呼ぶと良いです。ちなみに、今回は使用しませんが、関数の引数にはHTMLElementが入ります。

ここまで進むと、以下の画像のようになっていると思います。

image.png

これで大体OKです。残りはおまけ。

tokenの取得

App.tsx
import { useState } from "react";
import type PayjpJs from "typedef-payjp-js";
const payjp = window?.Payjp?.(
  "pk_test_token"
) as unknown as PayjpJs.Payjp;

const App = () => {
  const [token, setToken] = useState("");
  let cardElement: PayjpJs.PayjpElement | null = null;
  const handlePayDivMount = () => {
    const elements = payjp.elements();
    cardElement = elements.create("card");
    cardElement.mount("#v2-demo");
  };
  const handleGetToken = async () => {
    if (!cardElement) {
      alert("cardElement is not mounted");
      return;
    }
    const token = await payjp.createToken(cardElement);
    setToken(token.id);
  };

  return (
    <div style={{ maxWidth: "400px", backgroundColor: "#eee" }}>
      <div id="v2-demo" ref={handlePayDivMount} />
      <button type="button" onClick={handleGetToken}>
        get token
      </button>
      <div>token: {token}</div>
    </div>
  );
};

export default App;

buttonを押したらhandleGetToken関数が呼ばれ、トークンが生成されます。当たり前ですが、実際にアプリを作成する際にはtokenを表示しないように。

image.png

上掲した画像のようになっていればOK

まとめ

以上が基本的な使用方法となります。
デザインについては適宜styleで調整できます。このあたりはpayjp.jsのドキュメントをお読みください。
また、cardElementとして一体化したものではなく、カード番号入力欄、CVCなど個別になったものも同じように作成できます。

おまけ

App2.tsx
import type PayjpJs from "typedef-payjp-js";
const payjp = window?.Payjp?.("pk_test_token") as unknown as PayjpJs.Payjp;

const App2 = () => {
  let numberElement: PayjpJs.PayjpElement | null = null;
  let expiryElement: PayjpJs.PayjpElement | null = null;
  let cvcElement: PayjpJs.PayjpElement | null = null;

  const mounted = () => {
    const elements = payjp.elements();
    numberElement = elements.create("cardNumber", {
      style: {
        base: {
          color: "#fff",
          backgroundColor: "#000",
          fontSize: "20px",
        },
        invalid: {
          color: "#f00",
        },
      },
    });
    numberElement.mount("#number-form");
    expiryElement = elements.create("cardExpiry");
    expiryElement.mount("#expiry-form");
    cvcElement = elements.create("cardCvc");
    cvcElement.mount("#cvc-form");

    // Add event listener
    numberElement.on("change", (event) => {
      console.log(event);
    });
    expiryElement.on("change", (event) => {
      console.log(event);
    });
    cvcElement.on("change", (event) => {
      console.log(event);
    });
  };

  const getToken = async () => {
    if (!numberElement || !expiryElement || !cvcElement) {
      alert("element is not mounted");
      return;
    }
    const token = await payjp.createToken(numberElement);
    console.log(token);
  };
  return (
    <div style={{ maxWidth: "400px", backgroundColor: "#eee" }}>
      <div id="number-form" />
      <div id="expiry-form" />
      <div id="cvc-form" ref={mounted} />
      <button type="button" onClick={getToken}>
        get token
      </button>
    </div>
  );
};

export default App2;

こんな感じにするとnumber, expire, cvcを別個のフォームにできます。

1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?