快速入门: Stripe

快速开始使用Stripe

简介

Stripe是一个支付处理平台。用例包括:

  • 货币化访问昂贵的API调用
  • 货币化高级扩展功能
  • 销售主题、商品、实体和数字商品等

场景

你是一家SaaS公司,希望通过扩展向客户提供高级API服务。你希望用户在扩展可以访问此高级功能之前每月支付5美元。

设置Stripe产品链接

由于Manifest v3对远程代码执行的限制 (opens in a new tab),将符合PCI标准的支付系统集成到扩展中的选项有限。最简单的方法是设置一个Stripe产品链接。

要设置Stripe产品链接,必须创建一个Stripe产品。前往Stripe产品仪表板 (opens in a new tab)页面,然后点击添加产品,并填写信息:

Stripe添加产品

然后,进入产品页面并点击“创建支付链接”按钮:

Stripe创建支付

上述步骤应能获取到Stripe支付链接。对于后端授权,请前往Stripe仪表板主页 (opens in a new tab)获取密钥:

Stripe开发密钥

使用环境变量

假设你已经设置了一个基本的Plasmo项目,首先要做的就是设置我们的环境变量

env.development
PLASMO_PUBLIC_STRIPE_LINK=https://buy.stripe.com/test_XXXXXXXX
 
STRIPE_PRIVATE_API_KEY=sk_test_xxxxxxxxxxxxxxxxxxxxxx

为了启用TypeScript智能感知,创建一个index.d.ts文件:

.index.d.ts (opens in a new tab)

访问Chrome身份API

为了将订阅与用户关联,我们可以使用他们的电子邮件地址。一种快速的方法是利用Chrome扩展的身份API (opens in a new tab)。为防止未经授权的访问,我们需要设置OAuth2认证模式,其工作原理如下:

  • 我们的扩展生成OAuth2访问令牌
  • 扩展发送带有令牌的请求到我们的后端
  • 后端验证令牌以获取用户的电子邮件地址
  • 后端查询用户的订阅状态

要启用此功能所需的权限,请将以下内容添加到你的package.json文件的manifest字段中:

package.json
{
  ...
  "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>。它会显示如下内容:

GCP凭据页面

点击“创建凭据”,然后选择“OAuth客户端ID”:

创建OAuth客户端ID

在下一页,选择“Chrome应用”。表单将要求输入“应用程序ID”:

创建Chrome应用客户端ID

这将是你的扩展ID - 下一节将介绍如何获取它。

为开发设置固定的扩展ID

你会希望为开发固定你的扩展ID。如果你不小心从浏览器中删除了开发扩展,扩展ID将会丢失,并且你的OAuth2客户端将失效。

由于Chromium从公钥派生扩展ID,你可以通过生成自己的key来固定它。你可以在package.json的manifest覆盖中指定它。我们可以通过遵循这个Stack Overflow回答 (opens in a new tab)来生成此密钥:

  1. 生成私钥:
openssl genrsa 2048 | openssl pkcs8 -topk8 -nocrypt -out key.pem
  1. 从上面的私钥生成公钥:
openssl rsa -in key.pem -pubout -outform DER | openssl base64 -A

我们可以通过利用manifest覆盖中的环境变量来使用此密钥:

.env.development
...
CRX_PUBLIC_KEY=v47xxx
package.json
{
  "manifest": {
    ...
    "key": "$CRX_PUBLIC_KEY"
  }
}

运行开发服务器,然后将扩展加载到浏览器中。然后复制扩展ID:

复制扩展ID

将ID粘贴到OAuth表单的应用程序ID字段中,然后提交。你将收到OAuth2客户端ID:

OAuth客户端ID

将其添加到你的环境变量中:

.env.development
...
OAUTH_CLIENT_ID=<YOUR_OAUTH_CLIENT_ID>

并在我们的manifest覆盖中使用它:

package.json
{
  ...
  "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)。最简单的方法是使用puro (opens in a new tab) - Plasmo的上下文实用库。通过将库添加到你的package.json文件并运行pnpm i来安装它:

package.json
{
  ...
  "dependencies": {
    ...
    "puro": "0.3.4"
  }
}

然后,我们可以创建我们的提供者:

core/user-info.tsx (opens in a new tab)

core/user-info.tsx
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)

popup.tsx
import { UserInfoProvider, useUserInfo } from "~core/user-info"
 
const EmailShowcase = () => {
  const userInfo = useUserInfo()
 
  return (
    <div>
      你的电子邮件是: <b>{userInfo?.email}</b>
    </div>
  )
}
 
function IndexPopup() {
  return (
    <UserInfoProvider>
      <div
        style={{
          display: "flex",
          flexDirection: "column",
          padding: 16
        }}>
        <h1>
          欢迎使用你的 <a href="https://www.plasmo.com">Plasmo</a> 扩展!
        </h1>
        <EmailShowcase />
      </div>
    </UserInfoProvider>
  )
}
 
export default IndexPopup

将Stripe链接集成到弹出页面

为了简化Stripe支付链接与身份API的流程,我们可以通过它们的API参数 (opens in a new tab)预填Stripe托管表单上的电子邮件,该电子邮件是从上面的UserInfoProvicer获得的。在我们将用户重定向到Stripe支付之前,让我们也调用OAuth流程,以确保客户同意我们使用他们的电子邮件地址。这还将为我们的扩展启动访问令牌缓存,使未来的调用无需交互。

popup.tsx (opens in a new tab)

popup.tsx
<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"
          )
        }
      }
    )
  }}>
  订阅付费功能
</button>

验证订阅并启用一些高级功能

我们现在将设置后端以验证用户的订阅。我们可以通过利用NextJS与Plasmo的互操作性来简化此过程。我们将首先安装NextJS和一些实用库:

package.json
{
  "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)的现代化分支。

一旦我们设置了依赖项,让我们创建一些实用函数:

然后,我们创建两个API路由:一个用于检查用户的订阅,另一个用于调用高级功能。这两个API路由都必须首先解析授权头中的访问令牌,然后使用令牌独立地获取用户资料,再使用资料的数据获取用户的订阅。

要从我们的扩展调用开发服务器,我们可以使用环境变量存储API URI,并在manifest主机中引用它:

.env.development
PLASMO_PUBLIC_API_URI=http://localhost:8472
...
package.json
{
  ...
  "manifest": {
    ...
    "host_permissions": [
      "$PLASMO_PUBLIC_API_URI/*",
      "https://*/*"
    ]
  }
}

终止并重新运行pnpm dev以同时启动后端和扩展的开发服务器。在调用我们的API之前,让我们设置更多的客户端助手:

现在,我们可以使用swr来调用和重新验证弹出窗口中的check-subscription API:

popup.tsx
import useSWR from "swr"
import { callAPI } from "~core/premium-api"
 
...
  const { data, error } = useSWR<{ active: boolean }>(
    "/api/check-subscription",
    callAPI
  )
 
 
  if (!!error || !data?.active) {
    // 没有活动订阅,显示付款按钮
  }
 
  // 有活动订阅,显示高级功能按钮

然后,调用我们的高级功能:

popup.tsx
<button
  onClick={async () => {
    const data = await callAPI("/api/premium-feature", {
      method: "POST"
    })
 
    alert(data.code)
  }}>
  调用超棒的高级功能
</button>

完整示例

有关完整的示例,请查看GitHub存储库中的with-stripe (opens in a new tab)