Masonite Billing
Masonite Billing
需要
- Masonite 2.0.0+
安装
安装 Masonite Billing 很简单。我们只需要一个新的配置文件,两个新的迁移并扩展我们的用户模型。
Pip Install
$ pip install masonite-billing
添加服务提供者
只需将 Masonite Billing 服务提供者添加到您的提供者列表中:
from billing.providers import BillingProvider
PROVIDERS = [
...
# Third Party Providers
BillingProvider,
# Application Providers
...
]
这将添加一个新的 install:billing 命令。只需要如下方式执行:
$ craft install:billing
这将在 config/billing.py 中创建一个新的配置文件。
配置文件
所有 billing 信息都将位于 config/billing.py 文件中,并具有 2 个重要的常数:
...
DRIVER = 'stripe'
...
DRIVERS = {
'stripe': {
'client': os.getenv('STRIPE_CLIENT'),
'secret': os.getenv('STRIPE_SECRET'),
'currency': 'usd',
}
}
DRIVER 是 Masonite billing 将使用的处理器,而 DRIVERS 常数是驱动程序的配置设置。
尽管有 DRIVER 常量,但 Masonite Billing 当前仅支持 Stripe。其他驱动程序 (例如 Braintree) 将在向后兼容的更高版本中受支持。
迁移
我们将需要创建 2 个新迁移:一个迁移将列添加到 users 表,另一个迁移创建新的订阅表。只需使用 craft 创建这些迁移文件,然后将迁移复制并粘贴到这些文件中并进行迁移。
Let's first add 2 new columns to our users table.
$ craft migration add_subscription_info_to_users --table users
首先让我们向用户表添加 2 个新列。
with self.schema.table('users') as table:
table.string('customer_id').nullable()
table.string('plan_id').nullable()
现在,让我们添加一个新的订阅表。
$ craft migration create_subscriptions_table --create subscriptions
with self.schema.create('subscriptions') as table:
table.increments('id')
table.integer('user_id').unsigned()
table.string('plan')
table.string('plan_id')
table.string('plan_name')
table.timestamp('trial_ends_at').nullable()
table.timestamp('ends_at').nullable()
table.timestamps()
现在,只需运行新的迁移:
$ craft migrate
Stripe 身份验证密钥
只需将您的 Stripe 公钥和密钥添加到 .env 文件:
STRIPE_CLIENT=pk_Njsd993hdc...
STRIPE_SECRET=sk_sjs8yd78H8...
Billable Model
Masonite Billing 包含一个模型,该模型应由您想要向其添加订阅 billing 信息的任何模型继承。在此示例中,我们将重点放在将 billing 集成添加到用户模型中。
from config.database import Model
from billing.models.Billable import Billable
class User(Model, Billable):
__fillable__ = ['name', 'email', 'password']
__auth__ = 'email'
添加之后,我们现在将拥有大量可用于订阅和收费的方法。
在使用情况文档中阅读有关如何处理订阅和付款信息的更多信息。
入门
在下面,您会注意到我们正在使用 tok_amex 令牌,您可以将此令牌用于测试目的,但是生产中的此令牌应该是处理 stripe 表单时返回的令牌。
还需要注意的是,数据库中的订阅记录不会删除,而是会更新。因此,取消订阅不会从数据库中删除该订阅,而只会在订阅结束时进行设置。如果您想将数据转储到电子邮件活动工具中以尝试找回所有丢失的客户,这很有用。
订阅计划
要为用户订阅计划,我们可以像下面这样使用 subscribe 方法:
user.subscribe('masonite-test', 'tok_amex')
此方法重新调整订阅令牌的字符串,例如 sub_j8sy7dbdns8d7sd。
如果您尝试为用户订阅计划,但该计划在 Stripe 中不存在,则 Masonite 将抛出 billing.exceptions.PlanNotFound
异常。
检查订阅
如果要检查用户是否已订阅,则有几种选择:
您可以检查用户是否订阅了任何计划:
user.is_subscribed()
或者您可以检查用户是否订阅了特定计划:
user.is_subscribed('masonite-test')
您还可以检查用户是否已订阅但其计划已过期:
user.was_subscribed()
或者您可以显式检查用户是否已订阅特定计划:
user.was_subscribed('masonite-test')
获取计划名称
如果需要获取用户所在计划的名称,则可以使用 plan() 方法:
user.plan() # returns Masonite Test
这将返回在 Stripe 中的计划名称,而不是计划 ID。例如,我们的计划 ID 可能是 masonite-test,但计划名称可能是 「Awesome Plan」。
试用
如果您订阅的计划在 Stripe 中设置了试用版,则该用户将在该时间自动注册一个试用版。我们可以使用 on_trial() 方法检查用户是否正在试用。在我们这里的示例中,masonite-test 计划在 Stripe 中设置了 30 天的免费试用期。
例如:
user.subscribe('masonite-test') # plan has a 30 day trial
user.on_trial() # returns True
我们可能还希望跳过试用并立即向用户收取该计划的费用:
user.skip_trial().subscribe('masonite-test') # plan has a 30 day trial
user.on_trial() # returns False
我们还可以指定用户订阅计划时应试用的天数:
user.trial(days=4).subscribe('masonite-test') # 计划有 4 天的试用期
user.on_trial() # returns True
交换计划
我们可以随时使用 swap() 方法:
user.subscribe('masonite-test') # 计划有 30 天的试用期
user.is_subscribed('masonite-test') # returns True
user.on_trial() # returns True
user.swap('new-plan') # returns True
user.is_subscribed('masonite-test') # returns False
user.is_subscribed('new-plan') # returns True
取消
如果要取消用户的订阅,可以使用 cancel() 方法。
这将取消用户计划,但继续订阅,直到期限结束。
user.subscribe('masonite-test')
user.is_subscribed() # returns True
user.cancel() # returns True
user.is_subscribed() # returns True
请注意,即使取消后,最后一个 is_subscribed() 方法仍返回 True。用户将继续订阅,直到期限结束。例如,如果是 1 个月的订阅,而用户取消了 1 周的订阅,则 Masonite Billing 将继续订阅剩余的 3 周。
如果您希望立即取消用户,可以指定参数 now=True:
user.subscribe('masonite-test')
user.is_subscribed() # returns True
user.cancel(now=True) # returns True
user.is_subscribed() # now returns False
可以使用 user.is_canceled() 方法捕获取消订阅到耗尽的时间:
user.subscribe('masonite-test')
user.cancel() # returns True
user.is_canceled() # returns True
同样,只有在用户具有有效订阅但已选择取消订阅的情况下,此方法才返回 True。如果立即取消订阅,则将返回 False。
user.subscribe('masonite-test')
user.cancel(now=True) # returns True
user.is_canceled() # returns False
恢复计划
如果您不是立即取消计划,则在用户取消计划和恢复计划之间会有一段时间。我们可以在这里使用 resume() 方法:
user.subscribe('masonite-test')
user.cancel() # returns True
user.is_canceled() # returns True
user.resume() # returns True
user.is_canceled() # returns False
更新卡信息
有时用户会想要切换或更新其卡信息。我们可以使用 card() 方法并将令牌传递给它:
user.card('tok_amex') # updates the users card
用户计费
如果您想为客户进行一次交易,则可以使用 charge() 方法轻松实现:
您可以通过传递令牌来对卡计费:
user.charge(999, token='tok_amex') # 对用户卡计费
您也可以通过省略令牌来直接向客户收费,该令牌将向客户记录的任何卡收取费用。
user.charge(999) # 对用户计费
您还可以为费用添加描述和元数据 (作为词典):
user.charge(999, description='Charges for Flowers', metadata={'flower_type': 'tulips, roses'})
优惠券
您首先需要在 Stripe 中设置优惠券。
设置优惠券后,您可以在计费和订阅中使用优惠券:
user.coupon('black-friday').subscribe('masonite-plan')
您也可以传入整数以扣除金额:
user.coupon(500).charge(1000)
这将从您向用户收取的 10 美元中抵扣 5 美元。
您还可以制作优惠券以降低一定百分比:
user.coupon(.25).charge(1000)
这将抵扣 25% 的折扣。
Webhooks
Webhooks 允许您的应用程序在发生某些操作时 (例如当用户的订阅过期时) 直接与 stripe 交互。您可以使用这些 WebHooks 捕获发出的任何事件。
入门
Masonite Billing 还允许您绑定到 Stripe WebHook。如果在 Stripe 中取消订阅,则 Stripe 将向你的服务器发送 Webhook,而 Masonent Billing 将相应地更新数据库。
请确保在 Webhook 设置内的 Stripe 仪表板中设置正确的 URL。设置的网址应为 http://your-domain.com/stripe/webhook
,或者如果您正在使用 Ngrok 进行测试,则应为 http://684b1285.ngrok.io/stripe/webhook
。
如果您走 Ngrok 路由,请务必阅读 使用Ngrok 进行测试部分
Webhook 控制器
我们可以像其他控制器一样简单地将 webhook 控制器放置在 route/web.py 文件中,但是它可以指向 packages 控制器:
...
Post().route('/stripe/webhook', '/billing.controllers.WebhookController@handle')
...
这会将所有 Stripe 通信发送到服务器,并相应地处理所有挂钩。
使用 Ngrok 进行测试
大多数开发人员使用 Ngrok 来测试传入的 Webhooks。 Ngrok 是免费的 HTTP 隧道服务,允许外部请求通过隧道传输到本地主机和端口环境。非常适合测试像这样的东西以及像 OAuth 这样的东西。
如果使用 Ngrok,你会获得一个类似于:http://684b1285.ngrok.io
的子域。因为这是子域, Masonite 需要知道要支持哪个子域,所以您的路由必须为:
...
Post().domain('684b1285').route('/stripe/webhook', '/billing.controllers.WebhookController@handle')
...
为了捕获 Ngrok 子域。此设置将允许您将测试 WebHook 从 Stripe 发送到服务器。
您可以在路由文档的子域路由部分中阅读有关子域的更多信息。
CSRF保护
外部传入 API 调用通常无法受到 CSRF 保护,因为它们不知道连接到请求的特定令牌。我们需要对 /stripe/webhook 路径设置异常:
class CsrfMiddleware:
...
exempt = [
'/stripe/webhook'
]
创建自定义 Hooks
当前,webhook 控制器仅在取消订阅时处理,但可以处理您喜欢的任何 hook。如果需要创建自定义 hook,则可以从 WebhookController 继承并添加所需的控制器方法:
from billing.controllers import WebhookController
class CustomWebhookController(WebhookController):
def handle_resource_event(self):
return 'Webhook Handled'
路由
您还必须指定控制器的新位置,该位置现在应位于常规控制器目录中:
...
Post().route('/stripe/webhook', 'CustomWebhookController@handle'),
...
Events(事件)
事件是 Stripe 在发生某些操作时将发出的特定类型的 Webhook。
您可以在此处查看 stripe 事件列表: https://stripe.com/docs/api#event_types
您会注意到我们有一个 handle_resource_event 方法。 WebhookController 会将所有传入的 Webhook 事件带入 handle 方法,并将其分派给其他方法。
它是如何简单地处理事件,让我们说 charge.dispute.created Stripe 事件 (请参阅上面的链接以获取更多事件),然后将其解析为类似于以下内容的字符串:
它是如何做到这一点的,只是简单地获取事件,以 charge.disposte.created Stripe 事件为例,并将其解析为如下所示的字符串。(有关更多事件的列表,请参阅上面的链接):
handle_charge_dispute_created
您会注意到我们刚刚用 .
替换了 _
,并为其添加了 handle 前缀。这看起来像一个方法调用。如果我们现在简单地创建一个方法:
from billing.controllers import WebhookController
class CustomWebhookController(WebhookController):
def handle_charge_dispute_created(self):
return 'Webhook Handled'
每当我们收到该事件的 Webhook 时,就会调用此方法。如果找不到用于传入 Webhook 的方法,则服务器将返回不支持 Webhook 的字符串,您在开发测试 Stripe Webhooks 时可能会看到该字符串。
本译文仅用于学习和交流目的,转载请务必注明文章译者、出处、和本文链接
我们的翻译工作遵照 CC 协议,如果我们的工作有侵犯到您的权益,请及时联系我们。