django订阅
如果您熟悉Stripe,就会知道他们在在线支付处理领域中有多大的球员。 他们的API不仅使程序员可以轻松地为诸如电子商务商店之类的网站创建一次性付款,而且还为每月订阅和路由付款提供了快速集成。 如果对Django和Stripe不熟悉,请查看我们最近的有关一次性付款集成的文章 。 否则,让我们开始使用Django和Stripe设置每月付款。
为什么要按月订阅?
每月订阅是在网络上常见的一种惯例,特别是在那些推广软件即服务(SAAS)作为其交付模型的公司中。 例如,诸如Hubspot(市场营销),Dropbox(数据存储)和Mailchimp(电子邮件市场营销)之类的SAAS公司都为其潜在客户提供了分层定价选项。 考虑到一旦计算出基本指标(客户获取成本,生命周期价值,客户流失率)就更容易预测收入,许多人认为此模型是有利的。 当然,可预测的收入可以创造稳定并产生更准确的财务预测。
Mailchimp定价页面
Django设定
首先设置虚拟环境并创建一个基本的Django项目。 我们将创建一个名为saas
的新虚拟环境。
注意:在Mac上,对所有命令使用python3
而不是py
C:\Users\Owner\Desktop\code>py -m venv saas
接下来,将目录更改为虚拟环境,安装Django,然后设置您的项目和应用程序。
C:\Users\Owner\Desktop\code>cd saas
C:\Users\Owner\Desktop\code\saas>Scripts\activate
(saas) C:\Users\Owner\Desktop\code\saas>pip install Django
(saas) C:\Users\Owner\Desktop\code\saas>django-admin startproject mysite
(saas) C:\Users\Owner\Desktop\code\saas\mysite>py manage.py startapp main
将主应用程序添加到mysite中的settings.py中。
settings.py
INSTALLED_APPS = ['main.apps.MainConfig' , #add this
'django.contrib.admin' ,
'django.contrib.auth' ,
'django.contrib.contenttypes' ,
'django.contrib.sessions' ,
'django.contrib.messages' ,
'django.contrib.staticfiles' ,
]
在主文件夹中创建urls.py ,并将其包含在mysite> urls.py中 。
mysite> urls.py
from django.contrib import admin
from django.urls import path, include #add include
urlpatterns = [
path( '' , include( 'main.urls' )), #add path
path( 'admin/' , admin.site.urls),
]
条纹整合
首先安装用于连接Stripe API的官方库。
pip install --upgrade stripe
接下来,创建一个Stripe帐户,并在其仪表板中创建产品。 尽管可以使用Stripe CLI来执行此操作,但由于我们已经在创建帐户页面上,因此我们将使用仪表板。 确保您处于测试模式,并且只能查看测试数据。 默认情况下,创建帐户后,您应该处于测试模式。 单击左侧导航菜单中的产品。 创建两个新产品:
现在,让我们在Stripe仪表板中同步产品。 虽然我们可以创建模型并存储相关产品信息(例如产品ID)作为模型字段,但更简单的解决方案是仅安装dj-stripe程序包并使用sync命令。 我们还需要在settings.py中添加我们的API密钥。 请注意,您的活动密钥应始终受到保护,并且永远不会在设置中列出。 有关保护环境变量的更多信息,请查看Python保护。
pip install dj-stripe
mysite / settings.py
STRIPE_TEST_PUBLIC_KEY ='pk_test_E52Nh0gTCRpJ7h4JhuEX7BIO006LVew6GG'
STRIPE_TEST_SECRET_KEY = 'sk_test_L87kx7GQNbz9tajOluDts7da00mSbze3dW'
STRIPE_LIVE_MODE = False # Change to True in production
DJSTRIPE_WEBHOOK_SECRET = "whsec_xxx"
最后迁移数据库。 注意:如果数据库是sqlite,则迁移所需的时间比平时长。
py manage.py migrate
使用以下命令将产品自动添加到数据库:
py manage.py djstripe_sync_plans_from_stripe
查看管理页面以查看我们刚刚进行的更改。 首先创建一个管理员用户。 包括电子邮件,例如[email protected],因为我们将其用作Stripe客户名,然后访问http://127.0.0.1:8000/admin/ 。 您应该看到各种新模型,包括我们新创建的产品以及产品计划。
py manage.py createsuperuser
结帐页面
现在我们已经同步了数据,让我们创建一个结帐页面,用户可以在其中选择计划和结帐。 我们将从视图和签出模板开始。 注意:我们已经在home.html中导入了Bootstrap CDN:
views.py
from django.shortcuts import render, redirect
import stripe
import json
from django.http import JsonResponse
from djstripe.models import Product
from django.contrib.auth.decorators import login_required
# Create your views here.
def homepage (request) :
return render(request, "home.html" )
@login_required
def checkout (request) :
products = Product.objects.all()
return render(request, "checkout.html" ,{ "products" : products})
checkout.html
{% extends "home.html" %}
{% block content %}< script src = "https://js.stripe.com/v3/" > script >
< br > < br >
< div class = "container " >
< div class = "row " >
{% for p in products %}
< div class = "col-6" >
< div class = "card mx-5 shadow" style = "border-radius: 10px; border:none; " >
< div class = "card-body" >
< h5 class = "card-title font-weight-bold" > {{p.name}} h5 >
< p class = "card-text text-muted" > < svg class = "bi bi-check" width = "1em" height = "1em" viewBox = "0 0 16 16" fill = "currentColor" xmlns = "http://www.w3.org/2000/svg" >
< path fill-rule = "evenodd" d = "M10.97 4.97a.75.75 0 0 1 1.071 1.05l-3.992 4.99a.75.75 0 0 1-1.08.02L4.324 8.384a.75.75 0 1 1 1.06-1.06l2.094 2.093 3.473-4.425a.236.236 0 0 1 .02-.022z" />
svg > {{p.description}} p >
{% for plan in p.plan_set.all %}
< h5 > {{ plan.human_readable_price }} h5 >
< div class = "text-right" >
< input type = "checkbox" name = "{{p.name}}" value = "{{p.id}}" onclick = "planSelect('{{p.name}}' ,'{{plan.human_readable_price}}', '{{plan.id}}')" >
{% endfor %}
div >
div >
div >
div >
{% endfor %}
div >
< br > < br > < hr > < br > < br >
< div >
< div class = "row" >
< div class = "col-12" >
< div class = "card mx-5 shadow rounded" style = "border-radius:50px;border:none" >
< div class = "card-body" >
< h5 class = "card-title font-weight-bold" > Checkout h5 >
< p class = "text-muted " > Enter card details. Your subscription will start immediately p >
< div class = "row" >
< div class = "col-6 text-muted" >
< p > Plan: p >
< p > Total: p >
div >
< div class = "col-6 text-right" >
< p id = "plan" > p >
< p id = "price" > p >
< p hidden id = "priceId" > p >
div >
div >
< br >
< form id = "subscription-form" >
< div id = "card-element" class = "MyCardElement" >
div >
< div id = "card-errors" role = "alert" > div >
< button id = "submit" type = "submit" >
< div class = "spinner-border spinner-border-sm text-light hidden" id = "spinner" role = "status" >
< span class = "sr-only" > Loading... span >
div >
< span id = "button-text" > Subscribe span >
button >
form >
div >
div >
div >
div >
div >
div >
{% endblock %}
现在,我们有了用于选择订阅计划的模板。 让我们添加一些样式,并使用Stripe Elements(预先构建的一组UI组件)设置信用卡表单。
<style >
body {
.StripeElement {
box-sizing : border-box;
height : 40px ;
padding : 10px 12px ;
border : 1px solid transparent;
border-radius : 4px ;
background-color : white;
box-shadow : 0 1px 3px 0 #e6ebf1 ;
-webkit-transition : box-shadow 150ms ease;
transition : box-shadow 150ms ease;
}
.StripeElement--focus {
box-shadow : 0 1px 3px 0 #cfd7df ;
}
.StripeElement--invalid {
border-color : #fa755a ;
}
.StripeElement--webkit-autofill {
background-color : #fefde5 !important ;
}
.hidden {
display : none;
}
#submit :hover {
filter : contrast (120%);
}
#submit {
font-feature-settings : "pnum" ;
--body-color : #f7fafc ;
--button-color : #556cd6 ;
--accent-color : #556cd6 ;
--gray-border : #e3e8ee ;
--link-color : #fff ;
--font-color : #697386 ;
--body-font-family : -apple-system,BlinkMacSystemFont,sans-serif;
--radius : 4px ;
--form-width : 400px ;
-webkit-box-direction : normal;
word-wrap : break-word;
box-sizing : border-box;
font : inherit;
overflow : visible;
-webkit-appearance : button;
-webkit-font-smoothing : antialiased;
margin : 0 ;
font-family : inherit;
-webkit-tap-highlight-color : transparent;
font-size : 16px ;
padding : 0 12px ;
line-height : 32px ;
outline : none;
text-decoration : none;
text-transform : none;
margin-right : 8px ;
height : 36px ;
border-radius : var (--radius);
color : #fff ;
border : 0 ;
margin-top : 16px ;
font-weight : 600 ;
cursor : pointer;
transition : all . 2s ease;
display : block;
box-shadow : 0 4px 5.5px 0 rgba (0,0,0,.07);
width : 100% ;
background : var (--button-color);
}
style >
document .getElementById( "submit" ).disabled = true ;
stripeElements();
function stripeElements () {
stripe = Stripe( 'pk_test_E52Nh0gTCRpJ7h4JhuEX7BIO006LVew6GG' );
if ( document .getElementById( 'card-element' )) {
let elements = stripe.elements();
// Card Element styles
let style = {
base : {
color : "#32325d" ,
fontFamily : '"Helvetica Neue", Helvetica, sans-serif' ,
fontSmoothing : "antialiased" ,
fontSize : "16px" ,
"::placeholder" : {
color : "#aab7c4"
}
},
invalid : {
color : "#fa755a" ,
iconColor : "#fa755a"
}
};
card = elements.create( 'card' , { style : style });
card.mount( '#card-element' );
card.on( 'focus' , function () {
let el = document .getElementById( 'card-errors' );
el.classList.add( 'focused' );
});
card.on( 'blur' , function () {
let el = document .getElementById( 'card-errors' );
el.classList.remove( 'focused' );
});
card.on( 'change' , function ( event ) {
displayError(event);
});
}
//we'll add payment form handling here
}
function displayError ( event ) {
let displayError = document .getElementById( 'card-errors' );
if (event.error) {
displayError.textContent = event.error.message;
} else {
displayError.textContent = '' ;
}
}
这应允许信用卡表格呈现。 首先,我们禁用了订阅按钮,然后调用stripeElements()
创建然后挂载信用卡表格。 接下来,我们添加一个小脚本,以使用选定的订阅计划更新模板,并在选择计划后重新启用订阅按钮。
function planSelect ( name, price, priceId ) {
var inputs = document .getElementsByTagName( 'input' );
for ( var i = 0 ; ifalse ;
if (inputs[i].name== name){
inputs[i].checked = true ;
}
}
var n = document .getElementById( 'plan' );
var p = document .getElementById( 'price' );
var pid = document .getElementById( 'priceId' );
n.innerHTML = name;
p.innerHTML = price;
pid.innerHTML = priceId;
document .getElementById( "submit" ).disabled = false ;
}
现在,我们需要处理付款表格提交。 我们将第一个代码块添加到stripeElements()
函数的末尾。 首先通过表单的ID抓取表单,然后添加一个事件侦听器。 在阻止默认表单提交之后,我们更改了表单的加载状态,以防止与双击订阅按钮相关的任何问题。 最后,使用输入的卡信息,我们创建一种付款方式,并将此数据以及所选订阅的价格ID提交到我们的服务器。
如所示,我们使用Django的CSRF令牌来授权向我们服务器的提交。
注意:Stripe的文档包括一些其他验证,如果有兴趣,您应该检查一下。 另外,与文档不同,我们在一个请求中创建了一个客户并订阅了服务器,而不是发送单独的请求。
//we'll add payment form handling here
let paymentForm = document .getElementById( 'subscription-form' );
if (paymentForm) {
paymentForm.addEventListener( 'submit' , function ( evt ) {
evt.preventDefault();
changeLoadingState( true );
// create new payment method & create subscription
createPaymentMethod({ card });
});
}
}
function createPaymentMethod ( { card } ) {
// Set up payment method for recurring usage
let billingName = '{{user.username}}' ;
stripe
.createPaymentMethod({
type : 'card' ,
card : card,
billing_details : {
name : billingName,
},
})
.then( ( result ) => {
if (result.error) {
displayError(result);
} else {
const paymentParams = {
price_id : document .getElementById( "priceId" ).innerHTML,
payment_method : result.paymentMethod.id,
};
fetch( "/create-sub" , {
method : 'POST' ,
headers : {
'Content-Type' : 'application/json' ,
'X-CSRFToken' : '{{ csrf_token }}' ,
},
credentials : 'same-origin' ,
body : JSON .stringify(paymentParams),
}).then( ( response ) => {
return response.json();
}).then( ( result ) => {
if (result.error) {
// The card had an error when trying to attach it to a customer
throw result;
}
return result;
}).then( ( result ) => {
if (result && result.status === 'active' ) {
window .location.href = '/complete' ;
};
}).catch( function ( error ) {
displayError(result.error.message);
});
}
});
}
var changeLoadingState = function ( isLoading ) {
if (isLoading) {
document .getElementById( "submit" ).disabled = true ;
document .querySelector( "#spinner" ).classList.remove( "hidden" );
document .querySelector( "#button-text" ).classList.add( "hidden" );
} else {
document .getElementById( "submit" ).disabled = false ;
document .querySelector( "#spinner" ).classList.add( "hidden" );
document .querySelector( "#button-text" ).classList.remove( "hidden" );
}
};
添加用于创建订阅/客户以及付款完成时的网址路径。
main / urls.py
from django.urls import path
from . import views
app_name = "main"
urlpatterns = [
path( "" , views.homepage, name= "homepage" ),
path( "checkout" , views.checkout, name= "checkout" ),
path( "logout" , views.logout_request, name= "logout_request" ),
path( "login" , views.login_request, name= "logout_request" ),
path( "register" , views.register, name= "register" ),
path( "create-sub" , views.create_sub, name= "create sub" ), #add
path( "complete" , views.complete, name= "complete" ), #add
]
接下来,创建一个视图来处理创建Stripe客户和订阅。 客户和订阅也应存储在我们的本地数据库中。 幸运的是,我们可以使用dj-stripe轻松合成数据。
main / views.py
...import stripe
import json
from django.http import JsonResponse
from djstripe.models import Product
from django.contrib.auth.decorators import login_required
import djstripe
from django.http import HttpResponse
...
@login_required
def create_sub (request) :
if request.method == 'POST' :
# Reads application/json and returns a response
data = json.loads(request.body)
payment_method = data[ 'payment_method' ]
stripe.api_key = djstripe.settings.STRIPE_SECRET_KEY
payment_method_obj = stripe.PaymentMethod.retrieve(payment_method)
djstripe.models.PaymentMethod.sync_from_stripe_data(payment_method_obj)
try :
# This creates a new Customer and attaches the PaymentMethod in one API call.
customer = stripe.Customer.create(
payment_method=payment_method,
email=request.user.email,
invoice_settings={
'default_payment_method' : payment_method
}
)
djstripe_customer = djstripe.models.Customer.sync_from_stripe_data(customer)
request.user.customer = djstripe_customer
# At this point, associate the ID of the Customer object with your
# own internal representation of a customer, if you have one.
# print(customer)
# Subscribe the user to the subscription created
subscription = stripe.Subscription.create(
customer=customer.id,
items=[
{
"price" : data[ "price_id" ],
},
],
expand=[ "latest_invoice.payment_intent" ]
)
djstripe_subscription = djstripe.models.Subscription.sync_from_stripe_data(subscription)
request.user.subscription = djstripe_subscription
request.user.save()
return JsonResponse(subscription)
except Exception as e:
return JsonResponse({ 'error' : (e.args[ 0 ])}, status = 403 )
else :
return HTTPresponse( 'requet method not allowed' )
还为付款完成页面添加查看功能:
def complete (request) :
return render(request, "complete.html" )
通过测试集成。 根据Stripe的建议,使用“ 4242 4242 4242 4242”作为测试信用卡,并在提交付款后查看结果。 单击Stripe仪表板中“客户”下的“订阅”,以查看新创建的订阅和客户。 您应该看到类似以下内容:
结论
感谢您阅读有关使用Django和Stripe创建月度订阅的信息。
希望这为创建您自己的SAAS项目奠定了基础。 我们计划在Django和Stripe上编写其他文章,以涵盖诸如使用Stripe Connect进行付款,Lyft,Postmates,Kickstarter等背后的基础付款技术的主题。
另外,如果您想了解其他有关Django集成的建议,请在下面留下评论。
先前发布在 https://www.ordinarycoders.com/blog/article/django-stripe-monthly-subscription
翻译自: https://hackernoon.com/setting-up-subscriptions-and-recurring-payments-using-django-and-stripe-lh2d3ujc
django订阅