在上一章中,你創(chuàng)建了一個包括商品目錄和訂單系統(tǒng)的在線商店。你還學習了如何用Celery啟動異步任務。在這一章中,你會學習如何在網站中集成支付網關。你還會擴展管理站點,用于管理訂單和導出不同格式的訂單。 我們會在本章覆蓋以下知識點:
8.1 集成支付網關支付網關允許你在線處理支付。你可以使用支付網關管理用戶訂單,以及通過可靠的,安全的第三方代理處理支付。這意味著你不用考慮在自己的系統(tǒng)中存儲信用卡。 有很多支付網關可供選擇。我們將集成PayPal,它是最流行的支付網關之一。 PayPal提供了幾種方法在網站中集成它的網關。標準集成包括一個 8.1.1 創(chuàng)建PayPal賬戶你需要一個PayPal商家賬戶,才能在網站中集成支付網關。如果你還沒有PayPal賬戶,在這里注冊。確保你選擇了商家賬戶。 在注冊表單填寫詳細信息完成注冊。PayPal會給你發(fā)送一封郵件確認賬戶。 8.1.2 安裝django-paypal
在終端使用以下命令安裝django-paypal: pip install django-paypal 編輯項目的 INSTALLED_APPS = [ # ... 'paypal.standard.ipn',] 這個應用是django-paypal提供的,通過 在 # django-paypal settingsPAYPAL_RECEIVER_EMAIL = 'mypaypalemail@myshop.com'PAYPAL_TEST = True 這些設置分別是:
打開終端執(zhí)行以下命令,同步django-paypal的模型到數據庫中: python manage.py migrate 你會看到類似這樣結尾的輸出: Running migrations: Applying ipn.0001_initial... OK Applying ipn.0002_paypalipn_mp_id... OK Applying ipn.0003_auto_20141117_1647... OK Applying ipn.0004_auto_20150612_1826... OK Applying ipn.0005_auto_20151217_0948... OK Applying ipn.0006_auto_20160108_1112... OK Applying ipn.0007_auto_20160219_1135... OK 現在django-paypal的模型已經同步到數據庫中。你還需要添加django-paypal的URL模式到項目中。編輯 url(r'^paypal/', include('paypal.standard.ipn.urls')), 讓我們把支付網關添加到結賬過程中。 8.1.3 添加支付網關結賬流程是這樣的:
使用以下命令在項目中創(chuàng)建一個新應用: python manage.py startapp payment 我們將使用這個應用管理結賬流程和用戶支付。 編輯項目的 INSTALLED_APPS = [ # ... 'paypal.standard.ipn', 'payment',] 現在 from django.shortcuts import render, redirectfrom django.core.urlresolvers import reverse 找到 # launch asynchronous taskorder_created.delay(order.id)return render(request, 'orders/order/created.html', {'order': order}) 替換為下面的代碼: # launch asynchronous taskorder_created.delay(order.id)request.session['order_id'] = order.idreturn redirect(reverse('payment:process')) 創(chuàng)建訂單成功之后,我們用 編輯 from decimal import Decimalfrom django.conf import settingsfrom django.core.urlresolvers import reversefrom django.shortcuts import render, get_object_or_404from paypal.standard.forms import PayPalPaymentsFormfrom orders.models import Orderdef payment_process(request): order_id = request.session.get('order_id') order = get_object_or_404(Order, id=order_id) host = request.get_host() paypal_dict = { 'business': settings.PAYPAL_RECEIVER_EMAIL, 'amount': '%.2f' % order.get_total_cost().quantize(Decimal('.01')), 'item_name': 'Order {}'.format(order.id), 'invoice': str(order.id), 'currency_code': 'USD', 'notify_url': 'http://{}{}'.format(host, reverse('paypal-ipn')), 'return_url': 'http://{}{}'.format(host, reverse('payment:done')), 'cancel_return': 'http://{}{}'.format(host, reverse('payment:canceled')), } form = PayPalPaymentsForm(initial=paypal_dict) return render(request, 'payment/process.html', {'order': order, 'form': form}) 在
讓我們創(chuàng)建一個簡單的視圖,當支付完成,或者因為某些原因取消支付,讓PayPal重定向用戶。在同一個 from django.views.decorators.csrf import csrf_exempt@csrf_exemptdef payment_done(request): return render(request, 'payment/done.html')@csrf_exemptdef payment_canceled(request): return render(request, 'payment/canceled.html') 因為PayPal可以通過POST重定向用戶到這些視圖的任何一個,所以我們用 from django.conf.urls import urlfrom . import viewsurlpatterns = [ url(r'^process/$', views.payment_process, name='process'), url(r'^done/$', views.payment_done, name='done'), url(r'^canceled/$', views.payment_canceled, name='canceled'),] 這些是支付流程的URL。我們包括了以下URL模式:
編輯 url(r'^payment/', include('payment.urls', namespace='payment')), 記住把它放在 在 templates/ payment/ process.html done.html canceled.html 編輯 {% extends 'shop/base.html' %}{% block title %}Pay using PayPal{% endblock title %}{% block content %} 這個模板用于渲染 編輯 {% extends 'shop/base.html' %}{% block content %} 用戶支付成功后,會重定向到這個模板頁面。 編輯 {% extends 'shop/base.html' %}{% block content %} 處理支付遇到問題,或者用戶取消支付時,會重定向到這個模板頁面。 讓我們嘗試完整的支付流程。 8.1.4 使用PayPal的Sandbox在瀏覽器中打開 ![]() 最初,你會看到一個商家賬戶和一個PayPal自動生成的個人測試賬戶。你可以點擊 點擊列表中 ![]() 在 當你的網站使用sandbox環(huán)境時,測試賬戶可以用來處理支付。導航到 在終端執(zhí)行 ![]()
你可以看一眼HTML源碼,查看生成的表單字段。 點擊 ![]() 輸入顧客測試賬號的郵箱地址和密碼,然后點擊登錄按鈕。你會被重定向到以下頁面: ![]()
現在點擊 ![]() 點擊 ![]() 支付成功!但是因為我們在本地運行項目,127.0.0.1不是一個公網IP,所以PayPal不能給我們的應用發(fā)送支付狀態(tài)通知。我們接下來學習如何讓我們的網站可以從Internet訪問,從而接收IPN通知。 8.1.5 獲得支付通知IPN是大部分支付網關都會提供的方法,用于實時跟蹤購買。當網關處理完一個支付后,會立即給你的服務器發(fā)送一個通知。該通知包括所有支付細節(jié),包括狀態(tài)和用于確認通知來源的支付簽名。這個通知作為獨立的HTTP請求發(fā)送到你的服務器。出現問題的時候,PayPal會多次嘗試發(fā)送通知。
我們將創(chuàng)建一個自定義接收函數,并把它連接到 在 from django.shortcuts import get_object_or_404from paypal.standard.models import ST_PP_COMPLETEDfrom paypal.standard.ipn.signals import valid_ipn_receivedfrom orders.models import Orderdef payment_notification(sender, **kwargs): ipn_obj = sender if ipn_obj.payment_status == ST_PP_COMPLETED: # payment was successful order = get_object_or_404(Order, id=ipn_obj.invoice) # mark the order as paid order.paid = True order.save()valid_ipn_received.connect(payment_notification) 我們把
當 8.1.6 配置我們的應用你已經在第六章學習了應用配置。我們將為 在 from django.apps import AppConfigclass PaymentConfig(AppConfig): name = 'payment' verbose_name = 'Payment' def ready(self): # improt signal handlers import payment.signals 在這段代碼中,我們?yōu)?code>payment應用定義了一個 編輯 default_app_config = 'payment.apps.PaymentConfig' 這會讓Django自動加載你的自定義應用配置類。你可以在這里閱讀更多關于應用配置的信息。 8.1.7 測試支付通知因為我們在本地環(huán)境開發(fā),所以我們需要讓PayPal可以訪問我們的網站。有幾個應用程序可以讓開發(fā)環(huán)境通過Internet訪問。我們將使用Ngrok,是最流行的之一。 從這里下載你的操作系統(tǒng)版本的Ngrok,并使用以下命令運行: ./ngrok http 8000 這個命令告訴Ngrok在8000端口為你的本地主機創(chuàng)建一個鏈路,并為它分配一個Internet可訪問的主機名。你可以看到類似這樣的輸入: Session Status onlineAccount lakerszhy (Plan: Free)Update update available (version 2.2.4, Ctrl-U to update)Version 2.1.18Region United States (us)Web Interface http://127.0.0.1:4040Forwarding http://c0f17d7c. -> localhost:8000Forwarding https://c0f17d7c. -> localhost:8000Connections ttl opn rt1 rt5 p50 p90 0 0 0.00 0.00 0.00 0.00 Ngrok告訴我們,我們網站使用的Django開發(fā)服務器在本機的8000端口運行,現在可以通過
完成支付處理后,在瀏覽器中打開
你也可以在這里使用PayPal的模擬器發(fā)送IPN。模擬器允許你指定通知的字段和類型。 除了 8.2 導出訂單到CSV文件有時你可能希望把模型中的信息導出到文件中,然后把它導入到其它系統(tǒng)中。其中使用最廣泛的格式是 8.2.1 在管理站點你添加自定義操作Django提供了大量自定義管理站點的選項。我們將修改對象列表視圖,在其中包括一個自定義的管理操作。 一個管理操作是這樣工作的:用戶在管理站點的對象列表頁面用復選框選擇對象,然后選擇一個在所有選中選項上執(zhí)行的操作,最后執(zhí)行操作。下圖顯示了操作位于管理站點的哪個位置: ![]()
你可以編寫一個常規(guī)函數來創(chuàng)建自定義操作,該函數需要接收以下參數:
當在管理站點觸發(fā)操作時,會執(zhí)行這個函數。 我們將創(chuàng)建一個自定義管理操作,來下載一組訂單的CSV文件。編輯 import csvimport datetimefrom django.http import HttpResponsedef export_to_csv(modeladmin, request, queryset): opts = modeladmin.model._meta response = HttpResponse(content_type='text/csv') response['Content-Disposition'] = 'attachment;filename={}.csv'.format(opts.verbose_name) writer = csv.writer(response) fields = [field for field in opts.get_fields() if not field.many_to_many and not field.one_to_many] # Write a first row with header information writer.writerow([field.verbose_name for field in fields]) # Write data rows for obj in queryset: data_row = [] for field in fields: value = getattr(obj, field.name) if isinstance(value, datetime.datetime): value = value.strftime('%d/%m/%Y') data_row.append(value) writer.writerow(data_row) return responseexport_to_csv.short_description = 'Export to CSV' 在這段代碼中執(zhí)行了以下任務:
我們創(chuàng)建了一個通用的管理操作,可以添加到所有 最后,如下添加 calss OrderAdmin(admin.ModelAdmin): # ... actions = [export_to_csv] 在瀏覽器中打開 ![]() 選中幾條訂單,然后在選擇框中選擇 ID,first name,last name,email,address,postal code,city,created,updated,paid1,allen,iverson,lakerszhy@gmail.com,北京市朝陽區(qū),100012,北京市,11/05/2017,11/05/2017,False2,allen,kobe,lakerszhy@gmail.com,北京市朝陽區(qū),100012,北京市,11/05/2017,11/05/2017,False 正如你所看到的,創(chuàng)建管理操作非常簡單。 8.3 用自定義視圖擴展管理站點有時,你可能希望通過配置 讓我們創(chuàng)建一個自定義視圖,顯示訂單的相關信息。編輯 from django.contrib.admin.views.decorators import staff_member_requiredfrom django.shortcuts import get_object_or_404from .models import Order@staff_member_requireddef admin_order_detail(request, order_id): order = get_object_or_404(Order, id=order_id) return render(request, 'admin/orders/order/detail.html', {'order': order})
現在編輯 url(r'^admin/order/(?P 在 admin/ orders/ order/ detail.html 編輯 {% extends 'admin/base_site.html' %}{% load static %}{% block extrastyle %} {% endblock extrastyle %}{% block title %} Order {{ order.id }} {{ block.super }}{% endblock title %}{% block breadcrumbs %} {% endblock breadcrumbs %}{% block content %} 這個模板用于在管理站點顯示訂單詳情。模板擴展自Django管理站點的 為了使用靜態(tài)文件,我們可以從本章的示例代碼中獲得它們。拷貝 我們使用父模板中定義的塊引入自己的內容。我們顯示訂單信息和購買的商品。 當你想要擴展一個管理模板時,你需要了解它的結構,并確定它存在哪些塊。你可以在這里查看所有管理模板。 如果需要,你也可以覆蓋一個管理模板。把要覆蓋的模板拷貝到 最后,讓我們?yōu)楣芾碚军c的列表顯示頁中每個 from django.core.urlresolvers import reversedef order_detail(obj): return 'View'.format(reverse('orders:admin_order_detail', args=[obj.id]))order_detail.allow_tags = True 這個函數接收一個
然后編輯 class OrderAdmin(admin.ModelAdmin): list_display = [... order_detail] 在瀏覽器中打開 ![]() 點擊任何一個訂單的 ![]() 8.4 動態(tài)生成PDF單據我們現在已經有了完成的結賬和支付系統(tǒng),可以為每個訂單生成PDF單據了。有幾個Python庫可以生成PDF文件。一個流行的生成PDF文件的Python庫是Reportlab。你可以在這里查看如果使用Reportlab輸出PDF文件。 大部分情況下,你必須在PDF文件中添加自定義樣式和格式。你會發(fā)現,讓Python遠離表現層,渲染一個HTML模板,然后把它轉換為PDF文件更加方便。我們將采用這種方法,在Django中用模塊生成PDF文件。我們會使用WeasyPrint,它是一個Python庫,可以從HTML模板生成PDF文件。 8.4.1 安裝WeasyPrint首先,為你的操作系統(tǒng)安裝WeasyPrint的依賴,請訪問這里。 然后用以下命令安裝WeasyPrint: pip install WeasyPrint 8.4.2 創(chuàng)建PDF模板我們需要一個HTML文檔作為WeasyPrint的輸入。我們將創(chuàng)建一個HTML模板,用Django渲染它,然后把它傳遞給WeasyPrint生成PDF文件。 在 這是PDF單據的模板。在這個模板中,我們顯示所有訂單詳情和一個包括商品的HTML的 8.4.3 渲染PDF文件我們將創(chuàng)建一個視圖,在管理站點中生成已存在訂單的PDF單據。編輯 from django.conf import settingsfrom django.http import HttpResponsefrom django.template.loader import render_to_stringimport weasyprint@staff_member_requireddef admin_order_pdf(request, order_id): order = get_object_or_404(Order, id=order_id) html = render_to_string('orders/order/pdf.html', {'order': order}) response = HttpResponse(content_type='application/pdf') response['Content-Disposition'] = 'filename='order_{}.pdf''.format(order.id) weasyprint.HTML(string=html).write_pdf(response, stylesheets=[weasyprint.CSS(settings.STATIC_ROOT + 'css/pdf.css')]) return response 這個視圖用于生成訂單的PDF單據。我們用 因為我們需要使用 STATIC_ROOT = os.path.join(BASE_DIR, 'static/') 接著執(zhí)行 You have requested to collect static files at the destinationlocation as specified in your settings: /Users/lakerszhy/Documents/GitHub/Django-By-Example/code/Chapter 8/myshop/staticThis will overwrite existing files!Are you sure you want to do this? 輸入
編輯 url(r'admin/order/(?P 現在,我們可以編輯管理列表顯示頁面,為 def order_pdf(obj): return 'PDF'.format(reverse('orders:admin_order_pdf', args=[obj.id]))order_pdf.allow_tags = Trueorder_pdf.short_description = 'PDF bill' 把 class OrderAdmin(admin.ModelAdmin): list_display = [..., order_detail, order_pdf] 如果你為可調用對象指定了 在瀏覽器中打開 ![]() 點擊任意一條訂單的PDF鏈接。你會看到生成的PDF文件,下圖是未支付的訂單: ![]() 已支付訂單如下圖所示: ![]() 8.4.4 通過郵件發(fā)送PDF文件當收到支付時,讓我們給顧客發(fā)送一封包括PDF單據的郵件。編輯 from django.template.loader import render_to_stringfrom django.core.mail import EmailMessagefrom django.conf import settingsimport weasyprintfrom io import BytesIO 然后在 # create invoice e-mailsubject = 'My Shop - Invoice no. {}'.format(order.id)message = 'Please, find attached the invoice for your recent purchase.'email = EmailMessage(subject, message, 'admin@myshop.com', [order.email])# generate PDFhtml = render_to_string('orders/order/pdf.html', {'order': order})out = BytesIO()stylesheets = [weasyprint.CSS(settings.STATIC_ROOT + 'css/pdf.css')]weasyprint.HTML(string=html).write_pdf(out, stylesheets=stylesheets)# attach PDF fileemail.attach('order_{}.pdf'.format(order.id), out.getvalue(), 'application/pdf')# send e-mailemail.send() 在這個信號中,我們用Django提供的 記得在項目 現在打開Ngrok提供的應用URL,完成一筆新的支付,就能在郵件中收到PDF單據了。 8.5 總結在這一章中,你在項目中集成了支付網關。你自定義了Django管理站點,并學習了如果動態(tài)生成CSV和PDF文件。 下一章會深入了解Django項目的國際化和本地化。你還會創(chuàng)建一個優(yōu)惠券系統(tǒng)和商品推薦引擎。 |
|