发布于 2026-01-06 0 阅读
0

使用 Appwrite 和 Stripe 开始在线销售

使用 Appwrite 和 Stripe 开始在线销售

Appwrite是一款开源的后端即服务 (BaaS),它通过提供一套 REST API 来满足您的核心后端需求,从而抽象化构建现代应用程序的所有复杂性。Appwrite 可以处理用户身份验证和授权、实时数据库、云函数、Webhook 等等!如果缺少任何功能,您可以轻松地使用您喜欢的后端语言扩展 Appwrite。

每个网站都是独一无二的项目,有着各自独特的需求。Appwrite 深知后端定制的重要性,并提供Appwrite Functions来满足您的需求。Appwrite 目前支持 7 种编程语言,并且即将支持更多语言,让您能够使用最熟悉的语言来满足后端开发需求。

🚀 Appwrite 0.13 版本新增功能

Appwrite 0.13 引入了存储、CLI 和函数方面的新功能。凭借完全重写的执行模型,Appwrite 现在能够以低至 1 毫秒的速度运行函数!🤯 如此卓越的性能,Appwrite 现在还支持同步执行,允许您在一次 HTTP 请求中执行函数并获取响应。

得益于全新的执行模型,Appwrite 0.13 还允许创建全局变量,以便在函数的多次执行之间共享缓存。这显著提升了实际应用的性能,因为它们只需在首次执行时加载依赖项、启动第三方通信并预加载资源。

最后,Appwrite 新增了一个构建流程,可以在服务器端下载项目依赖项,因此您不再需要部署包含依赖项的函数。这大大简化了流程,减少了部署包的大小,也让开发者更加满意 😍

💸 使用 Stripe 进行在线支付

Stripe是一套功能强大的支付产品,可帮助您开展在线业务。Stripe 支持在线收款、设置和管理订阅、自动将税费计入付款、管理在线收据、生成结账页面等等。Stripe 支持多种支付方式和国家/地区,是任何在线产品的理想之选。

Stripe也深受开发者喜爱!在线支付很复杂……Stripe成功打造了一个快速、安全且易于理解的环境,让开发者能够在几分钟内设置好在线支付。

从技术角度来看,Stripe 付款是通过 REST API 发起的,付款状态更新则通过 Webhook 发送。接下来,我们将探讨如何使用 Appwrite Functions 与 Stripe 安全地通信,从而在应用程序中实现在线支付功能。

🙋 什么是 Webhook?

Webhook 是一种从一台服务器发送到另一台服务器的 HTTP 请求,用于通知对方某些变更。如果您需要快速适应外部变化,Webhook 比通过 API 服务器拉取数据更智能。

Stripe 使用 Webhook 来通知应用程序支付状态的变更。假设 Stripe 没有实现 Webhook,您如何知道支付是否成功?对于每个正在进行的支付,您需要一个循环,每隔几秒发送一次 API 请求来获取支付状态,直到状态发生变化为止。可想而知,这将是一个非常消耗资源的解决方案,并且无法很好地扩展,尤其是在同时处理大量待处理支付的情况下,在最糟糕的情况下甚至会达到 API 的调用限制。而有了 Webhook,您只需向 Stripe 提供一个 URL,Stripe 服务器就会向该 URL 发送 HTTP 请求,从而返回大量关于状态变更的数据。

与 Stripe 类似,Appwrite 也支持 Webhook,可以在 Appwrite 内部发生更改时(例如新用户注册或数据库更改)触发 HTTP 请求。这意味着 Appwrite 可以发送 Webhook,但它能接收 Webhook 吗?🤔

🪝 Appwrite Webhook 代理

得益于 Appwrite Functions,Appwrite 默认可以接收 webhook 请求。Appwrite HTTP API 中有一个端点可以创建函数执行。此方法允许传入数据并提供请求头。对于这样的 webhook 监听器来说,这就是所需的一切,但还有一个小问题。

查看Appwrite 文档可知,它需要一个 JSON 格式的请求体,其中所有数据都以字符串形式存储在指定data的键下。而查看Stripe 文档可知,它会发送一个 webhook,其中包含根目录下的所有数据,并以 JSON 对象的形式存储。

除了架构不匹配之外,Appwrite 还要求一些自定义标头(例如 API 密钥),而 Stripe 无法发送这些标头。这个问题可以通过一个简单的代理服务器来解决,该代理服务器能够正确地映射这两个架构,并应用身份验证标头。

您可以期待 Appwrite 官方的实现,但目前您可以使用Meldiron 的 Appwrite Webhook 代理。该项目会在您的 Appwrite 设置中添加一个配置,该配置定义了/v1/webhook-proxyAppwrite API 中的一个新的端点,以解决之前的问题。本文稍后将介绍如何设置此 Webhook 代理以及如何将其连接到 Stripe。

🛒 让我们一起编写商店代码吧

为了演示如何在 Appwrite 中集成 Stripe,我决定创建一个简单的应用cookie 商店,用户可以在其中购买两种 cookie 套餐中的一种。付款后,用户可以查看订单历史记录和付款状态。这是一个极简实现,不包含发票、订单履行或任何电子商务逻辑。该项目的设计理念是简洁易懂,旨在为任何将 Stripe 集成到 Appwrite 项目中的人员提供学习资源。

该应用程序使用NuxtJS框架和TypeScript编写,并使用Tailwind CSS设计实用类。您可以跟随教程进行操作,也可以从GitHub 代码库下载源代码

Stripe 设置

首先,让我们正确设置 Stripe 账户,确保我们拥有将来可能需要的所有密钥。本示例将使用测试模式,但同样的步骤也适用于生产环境。

首先访问Stripe网站并注册。进入控制面板后,切换到相应Developers页面并进入API keys选项卡。在该选项卡中,点击Reval key按钮并复制此密钥。稍后在 Appwrite 中设置createPayment函数时会用到它。

Stripe API 密钥

接下来,我们切换到该Webhooks选项卡,并设置一个新的端点。添加端点时,请务必使用 URL http://YOR_ENDPOINT/v1/webhook-proxy,并提供您想要的任何描述。最后,您需要选择要监听的事件。对于简单的在线支付,您只需要事件payment_intent.succeededpayment_intent.canceled

Stripe webhook 设置

添加端点后,复制您的代码,因为稍后在 Appwrite 函数Signing secret中需要用到它。updatePayment

Stripe webhook 秘密

Appwrite 项目设置

在开始前端开发之前,首先需要设置 Appwrite 项目。按照安装说明完成注册后,即可创建一个具有自定义项目 ID 的项目cookieShop

新的 Appwrite 项目

项目创建完成后,我们切换到页面Services上的相应选项卡Settings。在这里,您可以轻松禁用项目中不会用到的服务。您的应用程序只会用到账户服务、数据库服务和函数服务。请确保启用这三项服务,并禁用其余服务。

Appwrite 项目服务设置

最后,我们打开页面Settings上的标签页Users。在这里,您可以禁用除匿名会话之外的所有身份验证方法,因为您的应用程序将只使用匿名会话。

Appwrite 用户设置

完成所有这些配置后,您的 Appwrite 项目就准备就绪了!🎉

现在,您需要从 cookie store GitHub 仓库中应用程序化设置,该设置会配置数据库结构并准备 Appwrite 函数。克隆仓库并设置好 Appwrite CLI后,您只需运行命令即可appwrite deploy –all应用所有程序化设置。如果您有兴趣了解这些 Appwrite 函数的底层代码,可以在相应的文件夹中查看:

这些函数部署完成后,您需要设置它们的环境变量。Functions在 Appwrite 控制台中,打开函数Settings的选项卡createPayment。在设置接近末尾的位置,您需要添加一个名为 ` STRIPE_KEY<secretkey_name>` 的变量,其值为您在 Stripe 控制面板中获取的密钥。接下来,切换到 `<set_name>` 设置updatePayment,并在那里设置一些环境变量:

  • STRIPE_SIGNATURE- 来自 Stripe 控制面板的 Webhook 签名密钥。
  • APPWRITE_FUNCTION_ENDPOINT- Appwrite 实例的端点,位于Settings.
  • APPWRITE_FUNCTION_API_KEY- Appwrite 项目 API 密钥。您可以在左侧菜单中生成一个。

配置完成后,让我们来看看 Appwrite 函数实际是如何工作的!💻

应用程序写入函数

为了更好地理解 Appwrite 函数的逻辑,让我们来看一下它们的源代码。这两个函数都是用 Node.js 编写的。

1. 创建付款

首先,你需要将 Stripe 库添加到你的代码中,因为你将在这个函数中创建一个支付操作:



const stripe = require('stripe')


Enter fullscreen mode Exit fullscreen mode

接下来,您需要设置一个变量来保存所有可能的包装(产品)及其基本信息:



const packages = [
  {
    id: 'pack1',
    title: 'Medium Cookie Pack',
    description: 'Package incluces 1 cookie',
    price: 1.99,
    preview: '/pack1.jpg',
  },
  {
    id: 'pack2',
    title: 'Large Cookie Pack',
    description: 'Package incluces 6 cookies',
    price: 4.99,
    preview: '/pack2.jpg',
  },
]


Enter fullscreen mode Exit fullscreen mode

接下来,您需要设置一个函数,该函数将在创建执行项时执行:



module.exports = async function (req, res) {
  // Future code goes in here
}


Enter fullscreen mode Exit fullscreen mode

在您的函数内部,请确保该函数已在 Appwrite 中正确配置,并提供所需的环境变量:



  // Setup
  if (!req.env.STRIPE_KEY) {
    throw new Error('Environment variables are not set.')
  }


Enter fullscreen mode Exit fullscreen mode

接下来,我们来验证用户输入——有效载荷:



  // Prepate data
  const payload = JSON.parse(req.payload)
  const stripeClient = stripe(req.env.STRIPE_KEY)

  const package = packages.find((pack) => pack.id === payload.packId)

  if (!package) {
    throw new Error('Could not find the pack.')
  }


Enter fullscreen mode Exit fullscreen mode

接下来,您需要创建一个 Stripe 支付会话:



  // Create Stripe payment
  const session = await stripeClient.checkout.sessions.create({
    line_items: [
      {
        price_data: {
          currency: 'eur',
          product_data: {
            name: package.title,
            description: package.description,
          },
          unit_amount: package.price * 100,
        },
        quantity: 1,
      },
    ],
    mode: 'payment',
    success_url: payload.redirectSuccess,
    cancel_url: payload.redirectFailed,
    payment_intent_data: {
      metadata: {
        userId: req.env.APPWRITE_FUNCTION_USER_ID,
        packageId: package.id,
      },
    },
  })


Enter fullscreen mode Exit fullscreen mode

最后,我们返回 Stripe 支付会话 URL,以便客户端可以重定向到支付页面:



  // Return redirect URL
  res.json({
    paymentUrl: session.url,
  })


Enter fullscreen mode Exit fullscreen mode

2. 更新付款信息

与我们的第一个函数类似,您需要引入库并设置一个主函数:



const stripe = require('stripe')
const sdk = require('node-appwrite')

module.exports = async function (req, res) {
  // Future code goes in here
}


Enter fullscreen mode Exit fullscreen mode

您这次注意到导入了 Appwrite 吗?没错!当支付会话状态发生变化时,Stripe webhook 会执行此函数。这意味着您需要使用新的状态更新 Appwrite 文档,因此您需要与 API 建立正确的连接。

总之,接下来你要验证环境变量,但这次你还要初始化 Appwrite SDK:



  // Setup Appwrite SDK
  const client = new sdk.Client()
  const database = new sdk.Database(client)

  if (
    !req.env.APPWRITE_FUNCTION_ENDPOINT ||
    !req.env.APPWRITE_FUNCTION_API_KEY ||
    !req.env.STRIPE_SIGNATURE
  ) {
    throw new Error('Environment variables are not set.')
  }

  client
    .setEndpoint(req.env.APPWRITE_FUNCTION_ENDPOINT)
    .setProject(req.env.APPWRITE_FUNCTION_PROJECT_ID)
    .setKey(req.env.APPWRITE_FUNCTION_API_KEY)


Enter fullscreen mode Exit fullscreen mode

接下来,我们解析函数输入(有效载荷),并使用 Stripe 对其进行验证:



  // Prepate data
  const stripeSignature = req.env.STRIPE_SIGNATURE
  const payload = JSON.parse(req.payload)

  // Validate request + authentication check
  let event = stripe.webhooks.constructEvent(
    payload.body,
    payload.headers['stripe-signature'],
    stripeSignature
  )


Enter fullscreen mode Exit fullscreen mode

此外,您还可以解析 Stripe 事件数据,并选择与您的使用情况相关的信息:



  // Prepare results
  const status =
    event.type === 'payment_intent.succeeded'
      ? 'success'
      : event.type === 'payment_intent.canceled'
      ? 'failed'
      : 'unknown'

  const userId = event.data.object.charges.data[0].metadata.userId
  const packId = event.data.object.charges.data[0].metadata.packageId
  const paymentId = event.data.object.id

  const document = {
    status,
    userId,
    packId,
    paymentId,
    createdAt: Date.now(),
  }


Enter fullscreen mode Exit fullscreen mode

最后,我们添加一个逻辑,根据文档是否存在来更新或创建文档:



  // Check if document already exists
  const existingDocuments = await database.listDocuments(
    'orders',
    [`paymentId.equal('${paymentId}')`],
    1
  )

  let outcome

  if (existingDocuments.documents.length > 0) {
    // Document already exists, update it
    outcome = 'updateDocument'
    await database.updateDocument(
      'orders',
      existingDocuments.documents[0].$id,
      document,
      [`user:${userId}`],
      []
    )
  } else {
    // Document doesnt exist, create one
    outcome = 'createDocument'
    await database.createDocument(
      'orders',
      'unique()',
      document,
      [`user:${userId}`],
      []
    )
  }


Enter fullscreen mode Exit fullscreen mode

最后,我们将您刚才的操作作为响应返回,以便您可以在 Appwrite 控制台中检查执行响应,以便在需要再次确认特定付款中发生的情况时使用:



  res.json({
    outcome,
    document,
  })


Enter fullscreen mode Exit fullscreen mode

Appwrite Webhook 代理

如前所述,您需要使用Meldiron 的 webhook 代理将 Stripe 的 schema 转换为 Appwrite API 支持的 schema。为此,您需要在 Appwrite Docker 容器堆栈中添加一个新容器,这将为 Appwrite API 添加一个新的端点。

我们先docker-compose.yml在文件appwrite夹中的文件中添加一个新的容器定义:



version: "3"

services:
  appwrite-webhook-proxy:
    image: meldiron/appwrite-webhook-proxy:v0.0.4
    container_name: appwrite-webhook-proxy
    restart: unless-stopped
    labels:
      - "traefik.enable=true"
      - "traefik.constraint-label-stack=appwrite"
      - "traefik.docker.network=appwrite"
      - "traefik.http.services.appwrite_webhook_proxy.loadbalancer.server.port=4444"
      # http
      - traefik.http.routers.appwrite_webhook_proxy_http.entrypoints=appwrite_web
      - traefik.http.routers.appwrite_webhook_proxy_http.rule=PathPrefix(`/v1/webhook-proxy`)
      - traefik.http.routers.appwrite_webhook_proxy_http.service=appwrite_webhook_proxy
      # https
      - traefik.http.routers.appwrite_webhook_proxy_https.entrypoints=appwrite_websecure
      - traefik.http.routers.appwrite_webhook_proxy_https.rule=PathPrefix(`/v1/webhook-proxy`)
      - traefik.http.routers.appwrite_webhook_proxy_https.service=appwrite_webhook_proxy
      - traefik.http.routers.appwrite_webhook_proxy_https.tls=true
      - traefik.http.routers.appwrite_webhook_proxy_https.tls.certresolver=dns
    networks:
      - appwrite
    depends_on:
      - appwrite
    environment:
      - WEBHOOK_PROXY_APPWRITE_ENDPOINT
      - WEBHOOK_PROXY_APPWRITE_PROJECT_ID
      - WEBHOOK_PROXY_APPWRITE_API_KEY
      - WEBHOOK_PROXY_APPWRITE_FUNCTION_ID
  # ...
# ...


Enter fullscreen mode Exit fullscreen mode

完成上述步骤后,新的代理服务器将监听该/v1/webhook-proxy端点。

现在让我们更新.env同一appwrite文件夹中的文件,添加此容器进行安全通信所需的身份验证变量:



WEBHOOK_PROXY_APPWRITE_ENDPOINT=https://YOUR_ENDPOINT/v1
WEBHOOK_PROXY_APPWRITE_PROJECT_ID=YOUR_PROJECT_ID
WEBHOOK_PROXY_APPWRITE_API_KEY=YOUR_API_KEY
WEBHOOK_PROXY_APPWRITE_FUNCTION_ID=updatePayment


Enter fullscreen mode Exit fullscreen mode

最后,我们运行命令启动容器docker-compose up -d。一切就绪后,您现在可以将 Stripe 指向该容器https://YOR_ENDPOINT/v1/webhook-proxy,Stripe 将开始执行您的updatePayment函数,并以正确的模式提供所有数据。

前端网站搭建

本文的重点并非前端设计过程,因此,如果您对实现细节感兴趣,请务必查看该项目的GitHub 存储库。

解决了这个问题之后,我们来看看前端和 Appwrite 项目之间的通信。所有这些通信都在一个单独的appwrite.ts文件中实现,该文件包含以下函数:

  • 验证
  • 支付
  • 订单历史记录

在为这些服务编写函数之前,让我们先设置服务文件并完成所有初始设置:



import { Appwrite, Models } from "appwrite";

if (!process.env.appwriteEndpoint || !process.env.appwriteProjectId) {
    throw new Error("Appwrite environment variables not properly set!");
}

const sdk = new Appwrite();
sdk
    .setEndpoint(process.env.appwriteEndpoint)
    .setProject(process.env.appwriteProjectId);

const appUrl = process.env.baseUrl;

export type Order = {
    status: string,
    userId: string,
    packId: string,
    paymentId: string,
    createdAt: number
} & Models.Document;


Enter fullscreen mode Exit fullscreen mode

我们先来创建三个最重要的身份验证功能。你需要一个用于登录,一个用于注销,还有一个用于检查访客是否已登录。使用 AppwriteSDK,所有这些都可以在几行代码内完成:



export const AppwriteService = {
    async logout(): Promise<boolean> {
        try {
            await sdk.account.deleteSession("current");
            return true;
        } catch (err) {
            console.error(err);
            alert("Something went wrong. Please try again later.");
            return false;
        }
    },

    async login(): Promise<void> {
        await sdk.account.createAnonymousSession();
    },

    async getAuthStatus(): Promise<boolean> {
        try {
            await sdk.account.get();
            return true;
        } catch (err) {
            console.error(err);
            return false;
        }
    },

    // Future code goes in here
};


Enter fullscreen mode Exit fullscreen mode

接下来,您需要创建一个函数来触发我们之前编写的createPaymentAppwrite 函数,并利用url响应将用户重定向到 Stripe,以便他们支付订单:



    async buyPack(packId: string): Promise<boolean> {
        try {
            const executionResponse: any = await sdk.functions.createExecution("createPayment", JSON.stringify({
                redirectSuccess: `${appUrl}/cart-success`,
                redirectFailed: `${appUrl}/cart-error`,
                packId
            }), false);

            if (executionResponse.status === 'completed') {
            } else {
                throw new Error(executionResponse.stdout + "," + executionResponse.err);
            }

            const url = JSON.parse(executionResponse.stdout).paymentUrl;
            window.location.replace(url);

            return true;
        } catch (err) {
            console.error(err);
            alert("Something went wrong. Please try again later.");
            return false;
        }
    },


Enter fullscreen mode Exit fullscreen mode

最后,我们来实现一个获取用户订单历史记录的方法,该方法支持偏移分页:



    async getOrders(page = 1): Promise<Models.DocumentList<Order> | null> {
        try {
            const offset = (page - 1) * 10;
            const ordersResponse = await sdk.database.listDocuments<Order>("orders", undefined, 10, offset, undefined, undefined, ['createdAt'], ['DESC']);

            return ordersResponse;
        } catch (err) {
            console.error(err);
            alert("Something went wrong. Please try again later.");
            return null;
        }
    }


Enter fullscreen mode Exit fullscreen mode

登录功能全部就绪后,您只需创建页面、组件,并连接到我们的AppwriteServiceAppwrite 后端,即可完成前端应用程序的其余部分。

您已成功使用 Appwrite 和 Stripe 创建了自己的网店!👏 如果您对前端代码中省略的部分有任何疑问(这一点我必须强调),请务必查看该项目的整个GitHub 代码库,其中包含一个完整的演示应用程序。代码库中还有一些屏幕截图!👀

👨‍🎓 结论

对于任何可扩展的应用程序而言,将应用程序与第三方工具和 API 集成的能力都至关重要。正如您刚才体验到的,得益于 Appwrite 0.13,Appwrite Functions 现在可以双向通信,让您可以不受任何限制地准备项目。这不仅意味着您可以将几乎任何支付网关集成到 Appwrite 项目中,还意味着您可以尽情享受开发基于 Appwrite 的应用程序的乐趣,而无需任何限制!

如果您有项目想要分享、需要帮助,或者只是想成为 Appwrite 社区的一份子,我非常欢迎您加入我们的官方 Appwrite Discord 服务器。我迫不及待地想看看您的作品!

📚了解更多

您可以利用以下资源了解更多信息并获得帮助:

文章来源:https://dev.to/appwrite/start- sell-online-using-appwrite-and-stripe-3l04