この記事は、Stripe Apps を25日間紹介し続ける Advent Calendar 2022 20日目の記事です。
さまざまなサービスとStripe Appsアプリを連携させる場合、「すでにあるAPIサーバーやシステムと接続させる」必要が発生します。
今回はExpressを例に、安全にStripe Appsアプリと外部のAPIサーバーを連携させる方法を紹介します。
ExpressでStripe Appsと連携するAPIを作成する
Step0: プロジェクトのセットアップ
まずは簡単なExpressアプリを用意しましょう。
ライブリロードなどの機能を手軽に手に入れるため、今回はNxで作成します。
便宜上、今回はプロジェクト名をexpress-stripe
で統一して進めます。
$ npx create-nx-workspace --preset=express
Need to install the following packages:
create-nx-workspace@15.3.3
Ok to proceed? (y) y
> NX Let's create a new workspace [https://nx.dev/getting-started/intro]
✔ Repository name · express-stripe
✔ Application name · express-stripe
✔ Enable distributed caching to make your CI faster · No
セットアップ後、プロジェクトのディレクトリに移動してアプリを起動しましょう。
$ cd express-stripe
$ npm run start
chunk (runtime: main) main.js (main) 689 bytes [entry] [rendered]
webpack compiled successfully (24f80a260b1d420d)
Debugger listening on ws://localhost:9229/8faba312-c87d-47dc-b5e7-ef9d2d27b319
Debugger listening on ws://localhost:9229/8faba312-c87d-47dc-b5e7-ef9d2d27b319
For help, see: https://nodejs.org/en/docs/inspector
Type-checking in progress...
Listening at http://localhost:3333/api
No errors found.
APIの準備はできました。
ただし、CORSが未設定のため、ブラウザから呼び出すとエラーが発生します。
デモ用のAPIとして、apps/express-stripe/src/main.ts
を次のように変更しましょう。
const app = express();
+app.use((request, response, next) => {
+ response.header('Access-Control-Allow-Origin', '*');
+ response.header('Access-Control-Allow-Methods', 'GET,POST,OPTIONS');
+ response.header(
+ 'Access-Control-Allow-Headers',
+ 'Content-Type, Stripe-Signature'
+ );
+ if (request.method.toLocaleUpperCase() === 'OPTIONS') {
+ return response.send(200);
+ }
+ next();
+});
app.use('/assets', express.static(path.join(__dirname, 'assets')));
これでAPIが用意できました。
Step1: シンプルなAPIを連携させてみよう
さっそくStripe AppsのアプリとAPIを連携させてみましょう。
Stripe Appsアプリの、src/views/App.tsx
を次のように実装します。
import { ContextView, Box } from "@stripe/ui-extension-sdk/ui";
import { useEffect, useState } from 'react';
import { ExtensionContextValue } from "@stripe/ui-extension-sdk/context";
const App = (props: ExtensionContextValue) => {
const [message, setMessage] = useState("");
useEffect(() => {
fetch(new URL("http://localhost:3333/api"))
.then(response => response.json())
.then(data => {
setMessage(data.message);
})
.catch(console.log);
}, []);
return (
<ContextView
title="API呼び出しサンプル"
>
<Box>{message}</Box>
</ContextView>
);
};
export default App;
アプリを更新すると、Express APIからのレスポンスが画面に表示されます。
Step2: 署名検証を追加して、より安全に連携する
より安全にAPIサーバーを運用するために、Stripeが用意する署名検証の仕組みが利用できます。
開発中のアプリを、Stripeにアップロードする
署名シークレットを取得するには、Stripeアカウントにアプリを登録する必要があります。
そのため、Stripe CLIで、開発中のアプリを一度アップロードしましょう。
$ stripe apps upload
⬆ You are about to upload your Stripe App First Stripe App version "0.0.1" to テストアカウント(日本).
? Would you like to proceed? [Y/n] █
[y]を入力すると、ビルドとアップロードが始まります。
✔ Built files for production
✔ Packaged files for upload
✔ Uploaded First Stripe App
🌐 Press Enter to continue on the Stripe dashboard
と表示されますので、[Enterキー]を押してStripeダッシュボードに移動しましょう。
アプリの画面に移動できれば、アップロード完了です。
アップロードしたアプリは、公開操作を行うまでは利用できません。
「必要な情報を取得するために、アプリを登録する」くらいの気軽さでアップロードしてみてください。
ビルドエラーにご注意
アップロード前に、ビルドが行われます。
このビルド時にエラーが発生すると、アップロード処理が中断されます。
Would you like to proceed: y
× Failed to build files
✘ [ERROR] Expected identifier but found "{"
.build/manifest.js:23:2:
23 │ {
╵ ^
.build/
のコードエラーが出た場合は、rm -rf ./.build/
で中身を一度削除してみましょう。
アプリの署名シークレットを取得する
アプリを登録できたので、署名シークレットを取得しましょう。
アプリ詳細画面の[...]をクリックすると、[署名シークレット]が表示されます。
[署名シークレット]をクリックすると、アプリ用の署名シークレットが発行されます。
サーバー側で署名検証処理を実装する
署名シークレットを取得できたので、サーバー側で検証処理を組み込みましょう。
まずはStripe SDKをインストールします。
$ npm i stripe
続いて.env
を作成して、StripeのAPIキーと署名シークレットを環境変数に登録します。
STRIPE_SECRET_API_KEY=sk_test_xxx
STRIPE_APP_SECRET=absec_xxxx
apps/express-stripe/src/main.ts
を次のように変更しましょう。
import * as express from 'express';
import * as path from 'path';
+import Stripe from 'stripe';
+const stripe = new Stripe(process.env.STRIPE_SECRET_API_KEY as string, {
+ apiVersion: "2022-11-15"
+});
const app = express();
+app.use(express.json());
...
-app.get('/api', (request, response) => {
+app.post('/api', (request, response) => {
+ const sig = request.headers['stripe-signature'];
+ const payload = JSON.stringify(request.body);
+ try {
+ stripe.webhooks.signature.verifyHeader(payload, sig as string, process.env.STRIPE_APP_SECRET as string);
+ } catch (error) {
+ response.status(400).send(error.message);
+ return;
+ }
response.send({ message: 'Welcome to express-stripe!' });
});
アプリを再読み込みすると、APIがエラーを返すようになります。
アプリから、署名検証に対応したリクエストを送信する
サーバー側の準備ができましたので、アプリ側も署名検証に対応しましょう。
Stripe Appsアプリの、src/views/App.tsx
を次のように変更します。
import { ExtensionContextValue } from "@stripe/ui-extension-sdk/context";
+import fetchStripeSignature from '@stripe/ui-extension-sdk/signature';
const App = (props: ExtensionContextValue) => {
const [message, setMessage] = useState("");
+ const {
+ id: userId,
+ account: {
+ id: accountId,
+ }
+ } = props.userContext;
useEffect(() => {
- fetch(new URL("http://localhost:3333/api"))
+ if (!userId || !accountId) return;
+ fetchStripeSignature()
+ .then(signature => {
+ const signagurePayload = {
+ user_id: userId,
+ account_id: accountId,
+ }
+ return fetch(new URL("http://localhost:3333/api"), {
+ method: "POST",
+ headers: {
+ "Stripe-Signature": signature,
+ "Content-Type": "application/json"
+ },
+ body: JSON.stringify(signagurePayload)
+ })
+ })
.then(response => response.json())
.then(data => {
setMessage(data.message);
})
.catch(console.log);
}, [userId, accountId]);
return (
<ContextView
title="API呼び出しサンプル"
>
<Box>{message}</Box>
</ContextView>
);
};
変更を保存すると、API呼び出しが再度成功します。
終わりに
既存のAPIをStripe Appsから呼び出す場合にも、署名検証をサポートしたエンドポイントを作成することをお勧めします。
検証に利用するデータを追加することもできますので、ぜひ下記のドキュメントを参考にお試しください。
Stripe Appsひとりアドベントカレンダー 2022
今年ベータリリースされたばかりのStripe Appsは、まだ日本語の情報が多くありません。
そこでQiita Advent Calendar 2022にて、毎日Stripe Appsについての情報を投稿します。
ノーコードで利用する方法や、開発するためのTipsなども紹介予定ですので、ぜひ購読をお願いします。