基于Lemon Squeezy开发你的全球可用的会员功能
「会员功能系列文章」第一篇中《👉带你设计一套会员功能并开发它》,我们梳理了购买会员前后的处理逻辑,这一篇我们就来开发购买会员的功能。
选择支付工具
首先,我们要选择支付平台和工具。如果你的产品立足国内,那么用支付宝和微信支付就足够,如果你的产品还想放眼海外,我推荐使用Lemon Squeezy,原因有两个:
- Lemon Squeezy会根据用户所在地区提供不同的付费通道,其中包括微信、支付宝、信用卡和Paypal等全球常见的支付方式。
- Lemon Squeezy不仅仅是一个支付接入平台,它更像一个完整的解决方案,例如:它可以帮你管理订阅的自动续费,而微信支付、支付宝这样的渠道只有收付款功能,收付款以外的逻辑需要你自己设计开发。
上一篇文章我们设计了两个付费功能:月度会员和加油包。本文仍延续这样的会员方案设计,并基于Lemon Squeezy来开发它们。
关于Lemon Squeezy的知识就不赘述了,这些都是可以通过啃官方文档学到的知识,如果确有搞不清楚的地方,请评论区提问~。
欢迎加入「🌍独立全栈开发交流群」,一起赚美刀。
设置Lemon Squeezy产品
在Lemon Squeezy的后台,先创建一个产品,在创建产品的侧栏里,要添加两个vaiant,可以理解为同一个产品的不同型号,跟你网购时一样,添加不同型号可以方便用户在一个产品链接里切换自己想付费购买的服务。
滚动条滚到底部,在Confirmation modal里设置付费成功后的返回地址,默认是跳到Lemon Squeezy的订单页,建议设置为自己的网站,即用户进入付费页面前的URL
然后通过页面上和侧栏卡片里的「…
」按钮获取store_id
、product_id
和variant_id
,并加入环境变量
我们使用lemonsqueezy.ts
来进行开发
初始化lemonsqueezy
获取购买链接
我们需要在前端展示一个引导用户购买会员的区域,通过不同角色的权限对比,突出付费用户的可获得的优势,例如图片里这样:
中间的卡片是升级月度会员,右边的卡片是购买加油包。
卡片上的按钮是用来请求后台获取付费链接的:
后端添加对应接口
其中,attributes.checkout_data.custom
是自定义字段,可以把用户标识传进去,在用户付费完成后,Lemon Squeezy会一起把custom
内的信息传回来(下文介绍)。
接收Lemon Squeezy事件推送
在微信开发里,我们通常叫“事件推送”,在Lemon Squeezy里叫做Webhooks,它们的作用是一样,都是用来接收第三方平台推送的数据,用于闭环处理我们自己平台内的逻辑。
此时我们需要内网穿透,推荐使用Localtunnel或者VSCode最新版自带的穿透功能,可以看《👉内网穿透简介》。
有了穿透地址后,到Lemon Squeezy里设置Webhooks
如果你要用来接收事件推送的接口是/api/payment/webhooks
,那么Callback URL应该填入http://[YOUR URL]/api/payment/webhooks
。
Signing Secret就是环境变量里的LEMONS_SQUEEZY_SIGNATURE_SECRET
。
事件需勾选order_created
、subscription_created
、subscription_updated
。
现在,我们创建对应的API
出于安全考虑,在正式处理数据之前,我们需要先对签名进行校验,然后判断custom
里的字段有效性。
购买加油包属于一次性购买,购买阅读会员是按月续费,两种购买方式收到的数据结构是不一样的,可以通过first_order_item
字段进行区分,一次性购买的订单有这个字段(是一个包含订单核心信息的对象),而按月续费的则没有。
购买加油包
- 一次性购买只需要处理
order_created
事件
- Lemon Squeezy有一个推送机制,最多推送4次以确保我们能够闭环购买逻辑,所以我们需要记录订单信息,通过判断
identifier
(具备唯一性)是否是新订单来避免重复添加加油包
- Lemon Squeezy需要收到
status
为200的返回才会认为你正确处理了事件推送,如果status
不是200,则在后台可以看到错误信息
订阅月度会员
Lemon Squeezy可以帮我们实现记录订阅用户、续费方式、自动续费等多种付费后的逻辑处理,所以接收订阅阅读会员的逻辑没有调用上一篇文章实现的函数。保存会员信息的方式也被我改了,没有存在Redis里,而是直接存到Postgres数据库了。
- 按月订阅的推送数据从
payload.data.attributes
里读取
- 按月订阅的订单,Lemon Squeezy会在到期后默认进行续费,所以我们需要监听
subscription_created
和subscription_updated
两个事件,前者是创建订阅时触发,后者是创建订阅和更新订阅(包含续订、取消等)都会触发
subscription_created
的处理中,建议记录subcriptionId
(订阅的Id)、customerId
(Lemon Squeezy记录的用户Id)、variantId
(variant Id)和currentPeriodEnd
(到期时间)
subscription_updated
的处理中,需要更新variantId
和currentPeriodEnd
前端展示订阅信息
我们已经完成与Lemon Squeezy的功能对接,现在最后一步就是要把用户的订阅/购买信息展示给用户。
加油包的信息从Redis里读取就可以,和上一篇文章的逻辑一样。
按月订阅的信息则要从数据库中读取:
把加油包、月度会员、用户使用次数数据汇总到一个方法里:
服务端组件调用这个方法就可以获取到所有必备信息,并展示到前端了。
结语
无论你使用哪个支付平台和工具,开发起来的原理都差不多,只有两个要点:
- 获取支付页面链接
- 提供Webhooks接收支付平台的事件推送,然后根据事件进行相应处理
如果你还没理清楚会员功能的设计,请参考上一篇文章《👉带你设计一套会员功能并开发它》。
专栏资源
专栏介绍:以实战的角度进行Next.js生态圈的技术栈分享,内容包括但不限于:Next.js理论知识、功能模块设计思路、实战中使用到的技术栈。这是一个长期更新的专栏,我会持续把自己的思考和经验提炼分享出来,欢迎关注我的专栏👇
专栏地址:👉Next.js生态圈实战
专栏演示站:👉Next.js Demos
专栏源码仓库:👉Github - Source Code
交个朋友:👉加入「独立全栈交流群」