考虑到没有多少支付处理商提供它,创建一个市场可能太难了,或者是不可能的,如果他们不提供它,那么你很可能会在他们听到风声的那一刻就被踢出平台,即使没有如果您没有坚实的基础来处理使用该平台的卖家的付款、退款和付款,那么创建一个市场是有风险的。
Stripe Connect 解决了这些问题,它将使我们能够创建一个基本的市场,您可以在其中注册成为卖家,并且客户可以轻松地从这些卖家那里购买商品。作为平台所有者,您还可以设置服务费,因此当用户 X 从商店 Y 购买商品时,我们将获得该交易的 X% 分成,但稍后会详细介绍。
为了处理数据库连接,我们使用 Prisma,身份验证由 remix-auth 处理,对于这部分,我们仅处理市场的卖家端。
// This is your Prisma schema file, // learn more about it in the docs: https://pris.ly/d/prisma-schema generator client { provider = "prisma-client-js" } datasource db { provider = "postgresql" url = env("DATABASE_URL") } model Store { id String @id // This will be the store's subdomain name String updated_at DateTime @default(now()) @updatedAt seller Seller? } model Seller { id Int @id @default(autoincrement()) email String password String store Store @relation(fields: [store_id], references: [id]) date_created DateTime @default(now()) date_updated DateTime @updatedAt store_id String @unique }
这就是我们的 schema.prisma 文件的样子,我们有一个卖家模型和一个与之相关的商店模型,“id”字段将用作子域,因此当我们到达买家一侧时,我将能够访问 store.localhost.com 并从那里的卖家那里购买产品。
我们还将添加一个 Stripe 模型,它将存储有关卖家 Connect 帐户的数据。
model Stripe { account_id String @id is_onboarded Boolean @default(false) user Users @relation(fields: [user_id], references: [discord_id]) user_id String @unique created_at DateTime @default(now()) updated_at DateTime @updatedAt } model Seller { id Int @id @default(autoincrement()) email String password String store Store @relation(fields: [store_id], references: [id]) date_created DateTime @default(now()) date_updated DateTime @updatedAt store_id String @unique stripe Stripe? }
现在我们可以处理用户入门问题了,所以让我们在 .env 文件中定义另一个变量。
STRIPE_SK=your stripe secret key here
您可以通过在 Stripe 的开发页面中生成 Stripe 密钥来获取它,最好创建一个目前仅允许使用 Stripe Connect 的受限密钥。
然后您需要创建一个新文件来导出 Stripe 客户端,以便我们的路由可以使用它
// app/libs/stripe.server.ts import Stripe from 'stripe'; export const stripe = new Stripe(process.env.STRIPE_SK)
我们将创建一条位于“/onboarding”的新路线
// app/routes/onboarding.tsx export default function Onboarding() { const {stripe} = useLoaderData(); return <div className={'text-center pt-[6%]'}> <h1 className={'text-xl'}>Account onboarded: {stripe?.is_onboarded ? stripe?.account_id : '? Not connected'}</h1> <div className={'flex items-center text-white text-sm mt-5 justify-center gap-3'}> {!stripe ? <> <Form method={'post'}> <button type={'submit'} className={'bg-blue-600 hover:cursor-pointer rounded-[6px] px-4 py-1.5'}>Setup your seller account </button> </Form> </> : <> <div className={'bg-blue-600 rounded-[6px] px-4 py-1.5'}>Seller dashboard</div> </>} </div> </div> }
我们将添加一个加载器函数,该函数将传递有关卖家入职状态的数据
export async function loader({request}: LoaderFunctionArgs) { const user = await authenticator.isAuthenticated(request, { failureRedirect: '/login' }) const seller = await prisma.seller.findFirst({ where: { id: user.id }, include: { stripe: true } }) return { stripe: seller?.stripe } }
现在,如果您转到/onboarding,它会说您尚未连接,您将能够按下按钮进行注册,这就是我们的操作功能的用武之地
export async function action({request}: ActionFunctionArgs) { const authenticated = await authenticator.isAuthenticated(request, { failureRedirect: '/login' }) const seller = await prisma.seller.findFirst({ where: { id: authenticated.id }, include: { stripe: true } }) if (seller && seller.stripe?.is_onboarded) { return json({ message: 'User is onboarded already', error: true }, { status: 400 }) } const account = seller?.stripe?.account_id ? { id: seller.stripe?.account_id } : await stripe.accounts.create({ email: seller?.email, controller: { fees: { payer: 'application', }, losses: { payments: 'application', }, stripe_dashboard: { type: 'express', }, }, }); if (!seller?.stripe?.account_id) { await prisma.seller.update({ where: { id: authenticated.id }, data: { stripe: { create: { account_id: account.id } } }, include: { stripe: true } }) } const accountLink = await stripe.accountLinks.create({ account: account.id, refresh_url: 'http://localhost:5173/onboarding', return_url: 'http://localhost:5173/onboarding', type: 'account_onboarding', collection_options: { fields: 'eventually_due', }, }); console.debug(`[ACCOUNT ID = ${account.id}] CREATED ACCOUNT ONBOARDING LINK, REDIRECTING...`) return redirect(accountLink.url) }
当卖家按下按钮时,我们将使用他们注册时使用的电子邮件创建一个帐户,然后我们将创建一个帐户链接,将他们重定向到入职页面(如果卖家已经附加了 Stripe 帐户,但尚未加入,那么我们也会将他们重定向到加入链接。
从那里卖家将输入他的电子邮件/电话号码,然后入职流程将开始,Stripe 通常会询问卖家企业位置、企业详细信息、银行账户等...
现在我们可以监听 Stripe Connect 事件的 webhook,因此当卖家成功加入后,我们会将这些属性添加到数据库中的卖家记录中。
为了进行测试,您可以简单地下载 Stripe CLI,然后您可以将任何事件转发到我们现在将创建的新路由 /api/notifications
// This is your Prisma schema file, // learn more about it in the docs: https://pris.ly/d/prisma-schema generator client { provider = "prisma-client-js" } datasource db { provider = "postgresql" url = env("DATABASE_URL") } model Store { id String @id // This will be the store's subdomain name String updated_at DateTime @default(now()) @updatedAt seller Seller? } model Seller { id Int @id @default(autoincrement()) email String password String store Store @relation(fields: [store_id], references: [id]) date_created DateTime @default(now()) date_updated DateTime @updatedAt store_id String @unique }
当您运行该命令时,您将获得一个 Webhook 签名,以便我们可以验证 Stripe 发送给我们的每个 Webhook 的完整性,同样,如果您在 Stripe 上的开发人员门户上创建一个 Webhook,您将拥有一个秘密.
model Stripe { account_id String @id is_onboarded Boolean @default(false) user Users @relation(fields: [user_id], references: [discord_id]) user_id String @unique created_at DateTime @default(now()) updated_at DateTime @updatedAt } model Seller { id Int @id @default(autoincrement()) email String password String store Store @relation(fields: [store_id], references: [id]) date_created DateTime @default(now()) date_updated DateTime @updatedAt store_id String @unique stripe Stripe? }
我们还将在 .env 文件中添加一个新变量
STRIPE_SK=your stripe secret key here
现在我们可以编写代码来处理 Stripe 发送给我们的这些事件
// app/libs/stripe.server.ts import Stripe from 'stripe'; export const stripe = new Stripe(process.env.STRIPE_SK)
我们验证是否是 Stripe 发送了请求,如果是,那么我们继续,现在我们要关注的事件是 account.updated,该事件与我们在重定向卖家之前创建的帐户相关。
当卖家开始入职流程、添加电话号码、输入电子邮件或最终完成入职流程时,我们将收到“account.updated”事件,并且会发送此数组
account.requirements.currently_due
当“currently_due”数组的长度为零时,我们知道用户已完全注册,能够接受付款,因此从我们这边我们可以更新数据库并允许用户创建产品,但在此之前让我们添加'/api/notifications' 操作中的逻辑
// app/routes/onboarding.tsx export default function Onboarding() { const {stripe} = useLoaderData(); return <div className={'text-center pt-[6%]'}> <h1 className={'text-xl'}>Account onboarded: {stripe?.is_onboarded ? stripe?.account_id : '? Not connected'}</h1> <div className={'flex items-center text-white text-sm mt-5 justify-center gap-3'}> {!stripe ? <> <Form method={'post'}> <button type={'submit'} className={'bg-blue-600 hover:cursor-pointer rounded-[6px] px-4 py-1.5'}>Setup your seller account </button> </Form> </> : <> <div className={'bg-blue-600 rounded-[6px] px-4 py-1.5'}>Seller dashboard</div> </>} </div> </div> }
一旦到位,我们就可以尝试加入并看看它是否有效。例如,一旦您输入地址,您就会在项目的控制台中看到一条消息,例如
export async function loader({request}: LoaderFunctionArgs) { const user = await authenticator.isAuthenticated(request, { failureRedirect: '/login' }) const seller = await prisma.seller.findFirst({ where: { id: user.id }, include: { stripe: true } }) return { stripe: seller?.stripe } }
这意味着主体已经过验证,并且我们已成功接收来自 Stripe 的事件,但让我们看看加入是否有效。
一旦你到达最后一步,它可能会说你的帐户详细信息不完整,最后一步是身份验证,因为这是测试模式,我们可以模拟
好的,一旦我们完成了,我们将返回到上一页,我们可以按提交,按提交,我们将进入控制台
export async function action({request}: ActionFunctionArgs) { const authenticated = await authenticator.isAuthenticated(request, { failureRedirect: '/login' }) const seller = await prisma.seller.findFirst({ where: { id: authenticated.id }, include: { stripe: true } }) if (seller && seller.stripe?.is_onboarded) { return json({ message: 'User is onboarded already', error: true }, { status: 400 }) } const account = seller?.stripe?.account_id ? { id: seller.stripe?.account_id } : await stripe.accounts.create({ email: seller?.email, controller: { fees: { payer: 'application', }, losses: { payments: 'application', }, stripe_dashboard: { type: 'express', }, }, }); if (!seller?.stripe?.account_id) { await prisma.seller.update({ where: { id: authenticated.id }, data: { stripe: { create: { account_id: account.id } } }, include: { stripe: true } }) } const accountLink = await stripe.accountLinks.create({ account: account.id, refresh_url: 'http://localhost:5173/onboarding', return_url: 'http://localhost:5173/onboarding', type: 'account_onboarding', collection_options: { fields: 'eventually_due', }, }); console.debug(`[ACCOUNT ID = ${account.id}] CREATED ACCOUNT ONBOARDING LINK, REDIRECTING...`) return redirect(accountLink.url) }
成功了,现在 Stripe 将使我们返回到入门页面,它会向我们显示我们的帐户 ID,这意味着我们已成功入门,我们可以开始创建产品。
好吧,让我们先让卖家仪表板按钮发挥作用,然后再继续讨论产品,创建一条位于 /portal 的新路线
// This is your Prisma schema file, // learn more about it in the docs: https://pris.ly/d/prisma-schema generator client { provider = "prisma-client-js" } datasource db { provider = "postgresql" url = env("DATABASE_URL") } model Store { id String @id // This will be the store's subdomain name String updated_at DateTime @default(now()) @updatedAt seller Seller? } model Seller { id Int @id @default(autoincrement()) email String password String store Store @relation(fields: [store_id], references: [id]) date_created DateTime @default(now()) date_updated DateTime @updatedAt store_id String @unique }
非常基本的功能,因此现在当您登录 /portal 时,您将被重定向到我们为 Stripe 帐户生成的一次性链接。
在入职路线中,我们将使用链接包裹卖家仪表板 div。
model Stripe { account_id String @id is_onboarded Boolean @default(false) user Users @relation(fields: [user_id], references: [discord_id]) user_id String @unique created_at DateTime @default(now()) updated_at DateTime @updatedAt } model Seller { id Int @id @default(autoincrement()) email String password String store Store @relation(fields: [store_id], references: [id]) date_created DateTime @default(now()) date_updated DateTime @updatedAt store_id String @unique stripe Stripe? }
当我们访问 /portal 或按下按钮时,我们将被重定向到 Stripe 的 Connect 帐户门户,用户可以在那里看到他的分析、付款等...
这标志着我们使用 Stripe Connect 创建市场的第一部分的结束,第二部分将处理产品、付款和支出,第三部分将是最终部分,我们将处理项目面向客户的方面.
您可以在https://github.com/ddm50/stripe-connect-howto-seller查看该项目的源代码
以上是使用 Stripe Connect 创建市场:上线流程的详细内容。更多信息请关注PHP中文网其他相关文章!