使用 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函数时会用到它。
接下来,我们切换到该Webhooks选项卡,并设置一个新的端点。添加端点时,请务必使用 URL http://YOR_ENDPOINT/v1/webhook-proxy,并提供您想要的任何描述。最后,您需要选择要监听的事件。对于简单的在线支付,您只需要事件payment_intent.succeeded和payment_intent.canceled。
添加端点后,复制您的代码,因为稍后在 Appwrite 函数Signing secret中需要用到它。updatePayment
Appwrite 项目设置
在开始前端开发之前,首先需要设置 Appwrite 项目。按照安装说明完成注册后,即可创建一个具有自定义项目 ID 的项目cookieShop。
项目创建完成后,我们切换到页面Services上的相应选项卡Settings。在这里,您可以轻松禁用项目中不会用到的服务。您的应用程序只会用到账户服务、数据库服务和函数服务。请确保启用这三项服务,并禁用其余服务。
最后,我们打开页面Settings上的标签页Users。在这里,您可以禁用除匿名会话之外的所有身份验证方法,因为您的应用程序将只使用匿名会话。
完成所有这些配置后,您的 Appwrite 项目就准备就绪了!🎉
现在,您需要从 cookie store GitHub 仓库中应用程序化设置,该设置会配置数据库结构并准备 Appwrite 函数。克隆仓库并设置好 Appwrite CLI后,您只需运行命令即可appwrite deploy –all应用所有程序化设置。如果您有兴趣了解这些 Appwrite 函数的底层代码,可以在相应的文件夹中查看:
- createPayment(NodeJS)
- 更新支付(NodeJS)
这些函数部署完成后,您需要设置它们的环境变量。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')
接下来,您需要设置一个变量来保存所有可能的包装(产品)及其基本信息:
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',
},
]
接下来,您需要设置一个函数,该函数将在创建执行项时执行:
module.exports = async function (req, res) {
// Future code goes in here
}
在您的函数内部,请确保该函数已在 Appwrite 中正确配置,并提供所需的环境变量:
// Setup
if (!req.env.STRIPE_KEY) {
throw new Error('Environment variables are not set.')
}
接下来,我们来验证用户输入——有效载荷:
// 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.')
}
接下来,您需要创建一个 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,
},
},
})
最后,我们返回 Stripe 支付会话 URL,以便客户端可以重定向到支付页面:
// Return redirect URL
res.json({
paymentUrl: session.url,
})
2. 更新付款信息
与我们的第一个函数类似,您需要引入库并设置一个主函数:
const stripe = require('stripe')
const sdk = require('node-appwrite')
module.exports = async function (req, res) {
// Future code goes in here
}
您这次注意到导入了 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)
接下来,我们解析函数输入(有效载荷),并使用 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
)
此外,您还可以解析 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(),
}
最后,我们添加一个逻辑,根据文档是否存在来更新或创建文档:
// 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}`],
[]
)
}
最后,我们将您刚才的操作作为响应返回,以便您可以在 Appwrite 控制台中检查执行响应,以便在需要再次确认特定付款中发生的情况时使用:
res.json({
outcome,
document,
})
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
# ...
# ...
完成上述步骤后,新的代理服务器将监听该/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
最后,我们运行命令启动容器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;
我们先来创建三个最重要的身份验证功能。你需要一个用于登录,一个用于注销,还有一个用于检查访客是否已登录。使用 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
};
接下来,您需要创建一个函数来触发我们之前编写的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;
}
},
最后,我们来实现一个获取用户订单历史记录的方法,该方法支持偏移分页:
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;
}
}
登录功能全部就绪后,您只需创建页面、组件,并连接到我们的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





