Stripe のクイックスタート
はじめに
Stripe は決済処理プラットフォームです。使用例としては以下のようなものがあります。
- 高価なAPIコールへのアクセスを収益化
- プレミアム拡張機能の機能を収益化
- テーマ、グッズ、物理商品やデジタル商品の販売など
シナリオ
お客様に拡張機能を介してプレミアムAPIサービスを提供しようとしているSaaS企業であるとします。このプレミアム機能にアクセスするには、ユーザーに月額5ドルの料金を支払ってもらう必要があります。
Stripe Product Link の設定
Manifest v3の制限事項(リモートコード実行) (opens in a new tab)により、拡張機能にPCI準拠の決済システムを統合するオプションは限られています。最も簡単な方法は、Stripe Product Linkを設定することです。
Stripe Product Linkを設定するには、Stripe productを作成する必要があります。Stripe Product ダッシュボード (opens in a new tab)ページにアクセスし、「Add Product」ボタンを押して、情報を記入します。
次に、製品ページに移動し、「Create payment link」ボタンをクリックします。
これでStripe Payment Linkが取得できます。バックエンド認証のためには、シークレットキーを取得するためにStripeダッシュボードのホームページ (opens in a new tab)にアクセスしてください。
環境変数の使用
基本的なPlasmoプロジェクトが設定されていると仮定すると、最初に環境変数を設定する必要があります。
PLASMO_PUBLIC_STRIPE_LINK=https://buy.stripe.com/test_XXXXXXXX
STRIPE_PRIVATE_API_KEY=sk_test_xxxxxxxxxxxxxxxxxxxxxx
TypeScriptのIntelliSenseを有効にするには、index.d.ts
ファイルを作成します。
.index.d.ts
(opens in a new tab)
Chrome Identity APIへのアクセス
サブスクリプションをユーザーに関連付けるには、ユーザーのメールアドレスを使用できます。その簡単な方法は、Chrome拡張機能のIdentity API (opens in a new tab)を利用することです。不正アクセスを防ぐために、OAuth2認証スキームを設定する必要があります。その仕組みは以下のとおりです。
- 拡張機能がOAuth2アクセストークンを生成する
- 拡張機能がトークンを使用してバックエンドにリクエストを送信する
- バックエンドがトークンを検証してユーザーのメールアドレスを取得する
- バックエンドがユーザーのサブスクリプションの状態を照会する
この機能に必要な権限を有効にするには、package.json
ファイルのmanifest
フィールドに以下を追加します。
{
...
"manifest": {
...
"permissions": ["identity", "identity.email"]
}
}
...
は、すでに何かがある場合はそれを保持することを意味します。これは、多くのコード例で見られます。
次に、Google Cloud Platform(GCP)を使用してOAuth2クライアントIDを設定する必要があります。このガイド (opens in a new tab)に従ってGCPで新しいプロジェクトをすばやく作成し、資格情報ページに移動します:https://console.cloud.google.com/apis/credentials?referrer=search&project=<YOUR_PROJECT_ID>
。このような画面が表示されます。
「CREATE CREDENTIALS」をクリックし、「OAuth client ID」を選択します。
次のページで、「Chrome app」を選択します。フォームは「Application ID」を求めます。
これが拡張機能IDになります。次のセクションでは、その取得方法について説明します。
開発用の固定拡張機能IDの設定
開発用の拡張機能IDを固定することをお勧めします。開発用拡張機能をブラウザから誤って削除すると、拡張機能IDが失われ、OAuth2クライアントが無効になります。
Chromiumは公開鍵から拡張機能IDを取得するため、独自のkey
を生成することで固定できます。package.json
のマニフェスト上書きで指定できます。このキーは、Stack Overflowの回答 (opens in a new tab)に従って生成できます。
- 秘密鍵を生成します。
openssl genrsa 2048 | openssl pkcs8 -topk8 -nocrypt -out key.pem
- 上記の秘密鍵から公開鍵を生成します。
openssl rsa -in key.pem -pubout -outform DER | openssl base64 -A
次に、マニフェスト上書きでの環境変数を利用してこのキーを使用できます。
...
CRX_PUBLIC_KEY=v47xxx
{
"manifest": {
...
"key": "$CRX_PUBLIC_KEY"
}
}
開発サーバーを実行し、拡張機能をブラウザに読み込みます。次に、拡張機能IDをコピーします。
IDをOAuthフォームの「Application ID」フィールドに貼り付け、送信します。OAuth2クライアントIDが取得されます。
環境変数に追加します。
...
OAUTH_CLIENT_ID=<YOUR_OAUTH_CLIENT_ID>
そして、マニフェスト上書きで使用します。
{
...
"manifest": {
...
"oauth2": {
"client_id": "$OAUTH_CLIENT_ID",
"scopes": [
"https://www.googleapis.com/auth/userinfo.email",
"https://www.googleapis.com/auth/userinfo.profile"
]
}
}
}
これで、ユーザーのサブスクリプションを承認して処理するためのOAuthアクセストークンを生成する準備が整いました!
ユーザー情報のアクセス
chrome.identity.getProfileUserInfo
を使用して、ユーザーを特定できます。このデータをキャッシュしてアプリ全体で再利用するには、簡単なReactコンテキスト (opens in a new tab)を作成できます。最も簡単な方法は、Plasmoのコンテキストユーティリティライブラリであるpuro
(opens in a new tab)を使用することです。package.json
ファイルにライブラリを追加してpnpm i
を実行してインストールします。
{
...
"dependencies": {
...
"puro": "0.3.4"
}
}
次に、プロバイダーを作成します。
core/user-info.tsx
(opens in a new tab)
import { createProvider } from "puro"
import { useContext, useEffect, useState } from "react"
const useUserInfoProvider = () => {
const [userInfo, setUserInfo] = useState<chrome.identity.UserInfo>(null)
useEffect(() => {
chrome.identity.getProfileUserInfo((data) => {
if (data.email && data.id) {
setUserInfo(data)
}
})
}, [])
return userInfo
}
const { BaseContext, Provider } = createProvider(useUserInfoProvider)
export const useUserInfo = () => useContext(BaseContext)
export const UserInfoProvider = Provider
そして、ポップアップで使用します。
popup.tsx
(opens in a new tab)
import { UserInfoProvider, useUserInfo } from "~core/user-info"
const EmailShowcase = () => {
const userInfo = useUserInfo()
return (
<div>
Your email is: <b>{userInfo?.email}</b>
</div>
)
}
function IndexPopup() {
return (
<UserInfoProvider>
<div
style={{
display: "flex",
flexDirection: "column",
padding: 16
}}>
<h1>
Welcome to your <a href="https://www.plasmo.com">Plasmo</a> Extension!
</h1>
<EmailShowcase />
</div>
</UserInfoProvider>
)
}
export default IndexPopup
ポップアップページへのStripe Linkの統合
Identity APIとStripe決済リンクを効率化するために、上記UserInfoProvicer
から取得したメールアドレスを使用して、APIパラメータ (opens in a new tab)を介してStripeホストフォームにメールアドレスを事前に入力できます。ユーザーをStripe決済にリダイレクトする前に、顧客がメールアドレスの使用に同意することを確認するためにOAuthフローを呼び出しましょう。これにより、拡張機能のアクセストークンキャッシュも開始され、今後の呼び出しが非対話型になります。
popup.tsx
(opens in a new tab)
<button
disabled={!userInfo}
onClick={async () => {
chrome.identity.getAuthToken(
{
interactive: true
},
(token) => {
if (!!token) {
window.open(
`${process.env.PLASMO_PUBLIC_STRIPE_LINK}?client_reference_id=${
userInfo.id
}&prefilled_email=${encodeURIComponent(userInfo.email)}`,
"_blank"
)
}
}
)
}}>
Subscribe to Paid feature
</button>
サブスクリプションの検証とプレミアム機能の有効化
今度は、ユーザーのサブスクリプションを検証するためのバックエンドを設定します。NextJSとPlasmoの相互運用性を利用することで、このプロセスを簡素化できます。最初にNextJSと一部のユーティリティライブラリをインストールします。
{
"scripts": {
"start": "next start",
"dev": "run-p dev:*",
"dev:plasmo": "plasmo dev",
"dev:next": "next dev --port 8472",
"build": "run-p build:*",
"build:plasmo": "plasmo build",
"build:next": "next build"
},
...
"dependencies": {
...
"next": "12.1.6",
"google-auth-library": "8.0.2",
"swr": "1.3.0",
"stripe": "9.8.0"
},
"devDependencies": {
...
"@plasmohq/rps": "1.3.4",
}
}
@plasmohq/rps
は、スクリプトを並列または順次実行するのを容易にするPlasmoのヘルパーライブラリです。npm-run-all (opens in a new tab)の近代化されたフォークです。
依存関係を設定したら、いくつかのユーティリティ関数を作成しましょう。
次に、2つのAPIルートを作成します。1つはユーザーのサブスクリプションを確認するため、もう1つはプレミアム機能を呼び出すためのものです。どちらのAPIルートも、最初にアクセストークンの承認ヘッダーを解析し、トークンを使用してユーザーのプロファイルを個別に取得してから、プロファイルのデータを使用してユーザーのサブスクリプションを取得する必要があります。
pages/api/check-subscription.ts
(opens in a new tab)pages/api/premium-feature.ts
(opens in a new tab)
拡張機能から開発サーバーを呼び出すには、環境変数を使用してAPI URIを保存し、マニフェストホストで参照できます。
PLASMO_PUBLIC_API_URI=http://localhost:8472
...
{
...
"manifest": {
...
"host_permissions": [
"$PLASMO_PUBLIC_API_URI/*",
"https://*/*"
]
}
}
pnpm dev
をkillして再実行して、バックエンドと拡張機能の両方の開発サーバーを開始します。APIを呼び出す前に、クライアント側のヘルパーをさらに設定しましょう。
これで、swr
を使用してポップアップでcheck-subscription
APIを呼び出して再検証できます。
import useSWR from "swr"
import { callAPI } from "~core/premium-api"
...
const { data, error } = useSWR<{ active: boolean }>(
"/api/check-subscription",
callAPI
)
if (!!error || !data?.active) {
// アクティブなサブスクリプションがない場合、支払いボタンを表示する
}
// アクティブなサブスクリプションがある場合、プレミアム機能のボタンを表示する
次に、プレミアム機能を呼び出します。
<button
onClick={async () => {
const data = await callAPI("/api/premium-feature", {
method: "POST"
})
alert(data.code)
}}>
Calling Awesome Premium Feature
</button>
完全な例
完全な例については、サンプルGitHubリポジトリのwith-stripe (opens in a new tab)をご覧ください。