2025.03.08

RemixでStripeを使った決済機能の導入
- remix
- stripe
- react
はじめに
ReactをベースにしたフルスタックフレームワークであるRemixのアーキテクチャを使うことで、開発効率が上がり、Stripeとの統合もスムーズになります。
本記事では、RemixとStripeを活用して決済機能を導入する方法を記録しています。
"@remix-run/dev": "^2.16.0",
"stripe": "^17.7.0",
環境の準備
まず、stripe関連のパッケージをインストールします。
npm i stripe @stripe/stripe-js @stripe/react-stripe-js
これらのパッケージの役割は以下の通りです。
stripe
:サーバー側から Stripe API を操作するためのライブラリ@stripe/stripe-js
:クライアント側で Stripe.js を読み込むためのライブラリ@stripe/react-stripe-js
:React で Stripe を統合するためのコンポーネントやフックを提供
今回、ディレクトリ構造は次のようにしました。
./
├─ app/
│ ├─ components/
│ │ └─ stripe/
│ │ └─ payment_form.tsx
│ ├─ routes/
│ │ ├─ payment.tsx
│ │ └─ purchased.tsx
│ └─ utils/
│ └─ stripe/
│ ├─ client.ts
│ └─ server.ts
├─ .env
環境変数の設定
Stripe のシークレットキーを Stripe のダッシュボードで 取得し、 .env
ファイルに設定します。
STRIPE_SECRET_KEY=sk_test...
Stripe の設定
サーバー側、クライアント側とそれぞれの処理を共通化します。
import Stripe from "stripe";
export const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!, {
apiVersion: "2025-02-24.acacia",
typescript: true,
httpClient: Stripe.createFetchHttpClient(),
});
export async function createPaymentIntent(amount: number) {
return await stripe.paymentIntents.create({
amount,
currency: "jpy",
automatic_payment_methods: { enabled: true },
});
}
Stripe の公開可能キーを設定し、クライアントを初期化。
import { loadStripe } from "@stripe/stripe-js";
export const stripePromise = loadStripe("pk_test...");
決済フォームの作成
ユーザーがクレジットカード情報を入力する決済フォームです。後々カスタマイズしやすいように、ElementsをNumber, Expiry, Cvcと個別で読み込んでいます。
import { useStripe, useElements, CardNumberElement, CardExpiryElement, CardCvcElement } from "@stripe/react-stripe-js";
import { Stripe, StripeElements } from "@stripe/stripe-js";
import { FormEvent } from "react";
export default function PaymentForm({
onSubmit,
}: {
onSubmit: (e: FormEvent<HTMLFormElement>, stripe: Stripe | null, elements: StripeElements | null) => void;
}) {
const stripe = useStripe();
const elements = useElements();
const handleSubmit = (e: FormEvent<HTMLFormElement>) => {
if (!stripe || !elements) {
console.error("Stripe.js not loaded") ;
return;
}
onSubmit(e, stripe, elements);
};
return (
<form onSubmit={handleSubmit}>
<div>
<label htmlFor="card-number">Card Number</label>
<CardNumberElement id="card-number" />
</div>
<div>
<label htmlFor="card-expiry">Expiry Date</label>
<CardExpiryElement id="card-expiry" />
</div>
<div>
<label htmlFor="card-cvc">CVC</label>
<CardCvcElement id="card-cvc" />
</div>
<button>Complete Payment</button>
</form>
);
}
フォームの送信処理
クライアント側でStripe に決済情報を送信する処理を追加します。以下では決済が成功した時は遷移だけ行なっていますが、サーバー側の処理は action にデータを渡していくことになります。
また、/purchasedページの実装は割愛しています。
import { json, LoaderFunction } from "@remix-run/node";
import { useLoaderData } from "@remix-run/react";
import { Elements, CardNumberElement } from "@stripe/react-stripe-js";
import { Stripe, StripeElements } from "@stripe/stripe-js";
import { useEffect, useState, FormEvent } from "react";
import { useNavigate } from "react-router-dom";
import PaymentForm from "~/components/stripe/payment_form";
import { stripePromise } from "~/utils/stripe/client";
import { createPaymentIntent } from "~/utils/stripe/server";
const amount = 1000;//金額の設定
export const loader: LoaderFunction = async () => {
const paymentIntent = await createPaymentIntent(amount);
return json({ clientSecret: paymentIntent.client_secret });
};
export default function PaymentPage() {
const { clientSecret } = useLoaderData<{ clientSecret: string | null }>();
const [clientSecretState, setClientSecretState] = useState<string | null>(null);
const navigate = useNavigate();
useEffect(() => {
setClientSecretState(clientSecret);
}, [clientSecret]);
const handleSubmit = async (e: FormEvent<HTMLFormElement>, stripe: Stripe | null, elements: StripeElements | null) => {
e.preventDefault();
if (!stripe || !elements) return console.error("Stripe.js not loaded");
const cardNumberElement = elements.getElement(CardNumberElement);
if (!cardNumberElement) return console.error("Card element not found");
try {
const { error, paymentMethod } = await stripe.createPaymentMethod({
type: "card",
card: cardNumberElement,
});
if (error) return console.error("PaymentMethod error:", error);
const { error: confirmError } = await stripe.confirmCardPayment(clientSecretState!, {
payment_method: paymentMethod.id,
});
if (confirmError) {
console.error("Payment failed", confirmError);
} else {
navigate("/purchased");
}
} catch (error) {
console.error("Error:", error);
}
};
return (
<>
<p>Amount: {amount}</p>
{clientSecretState ?
<Elements stripe={stripePromise} options={{ clientSecret: clientSecretState }}>
<PaymentForm onSubmit={handleSubmit} />
</Elements>
: <p>Loading...</p>}
</>
);
}
以上
これで、Remix と Stripe を使った基本的な決済処理が行えたかと思います。Remix の強力な機能を活用することで、Stripe との統合をスムーズに行うことができました。少しでも参考になれば幸いです!