django 實現電子支付功能


  思路:調用第三方支付 API 接口實現支付功能。本來想用支付寶來實現第三方網站的支付功能的,但是在實際操作中發現支付寶沒有 Python 接口,網上雖然有他人二次封裝的的 Python 接口,但是對我這個小白白來說上手還是有點難度,后來發現 PayPal 有現成的 Django 模塊,想着以學習的目的來實現這一功能(其實還是自己辣雞),就決定以 PayPal 的電子支付功能來練手。

 

首先,安裝 PayPal 的 Django 模塊:django-paypal,具體介紹可以參考 GitHub上說明:https://github.com/spookylukey/django-paypal

pip install django-paypal

 

然后在 settings.py 中的 INSTALLED_APPS 將 'paypal.standard.ipn' 加入。並在 settings.py 中添加下列語句。

# 此付款機制作為測試用
PAYPAL_TEST = True # 設置收款的 PayPal 電子郵件賬戶
PAYPAL_REVEIVER_EMAIL = 'your email'

 

執行同步數據庫操作。

./manage.py migrate

 

urls.py 中加入下列樣式。分別為付款完成通知,處理賬務,顯示完成付款,取消付款操作。

url(r'^paypal/', include('paypal.standard.ipn.urls')),  # 付款完成通知
url(r'^payment/(\d+)/$', views.payment), url(r'^done/$', views.payment_done), url(r'^canceled/$', views.payment_canceled),

 

PayPal 付款操作,建立含有正確數據的付款按鈕。

@login_required def payment(request, order_id): all_categories = models.Category.objects.all() try: order = models.Order.objects.get(id=order_id) except: messages.add_message(request, messages.WARNING, "訂單編號錯誤,無法處理付款。") return redirect('/myorders/') all_order_items = models.OrderItem.objects.filter(order=order) items = list() total = 0 for order_item in all_order_items: t = dict() t['name'] = order_item.product.name t['price'] = order_item.product.price t['quantity'] = order_item.quantity t['subtotal'] = order_item.product.price * order_item.quantity total = total + order_item.product.price items.append(t) host = request.get_host() paypal_dict = { "business": settings.PAYPAL_REVEIVER_EMAIL, "amount": total, "item_name": "迷你小電商商品編號:{}".format(order_id), "invoice": "invoice-{}".format(order_id), "currency_code": 'CNY', "notify_url": "http://{}{}".format(host, reverse('paypal-ipn')), "return_url": "http://{}/done/".format(host), "cancel_return": "http://{}/canceled/".format(host), } paypal_form = PayPalPaymentsForm(initial=paypal_dict) template = get_template('payment.html') html = template.render(context=locals(), request=request) return HttpResponse(html)

由於用到了 django-paypal 提供的 PayPalPaymentForm 類。因此在 views.py 的前面也要導入這個類。另外,因為用到了 settings.py 中的常數,所以也要導入 settings,語句如下:

from django.conf import settings
from paypal.standard.forms import PayPalPaymentsForm
from django.core.urlresolvers import reverse

 

付款完成。

@csrf_exempt    #csrf 驗證
def payment_done(request): template = get_template('payment_done.html') html = template.render(context=locals(), request=request) return HttpResponse(html)

 

取消付款。

@csrf_exempt def payment_canceled(request): template = get_template('payment_canceled.html') html = template.render(context=locals(), request=request) return HttpResponse(html)

 

PayPal 付款頁面。

<!-- payment.html (mshop project) --> {% extends "base.html" %} {% block title %}選擇您的付款方式{% endblock %} {% block content %} <div class='container'> {% for message in messages %} <div class='alert alert-{{message.tags}}'>{{ message }}</div> {% endfor %} <div class='row'>
        <div class='col-md-12'>
            <div class='panel panel-default'>
                <div class='panel-heading' align=center>
                    <h3>歡迎光臨迷你小電商</h3> {% if user.socialaccount_set.all.0.extra_data.name %} {{user.socialaccount_set.all.0.extra_data.name}}<br/>
                            <img src='{{user.socialaccount_set.all.0.get_avatar_url}}' width='100'> {% else %} Welcome: {{ user.username }} {% endif %} </div>
            </div>
        </div>
    </div>
    <div class='row'>
        <div class='col-sm-12'>
            <div class='panel panel-info'>
                <div class='panel panel-heading'>
                    <h4>在線付款(訂單編號:{{order.id}})</h4>
                </div>
                <div class='panel panel-body'> {% for item in items %} {% if forloop.first %} <table border=1>
                        <tr>
                            <td width=300 align=center>產品名稱</td>
                            <td width=100 align=center>單價</td>
                            <td width=100 align=center>數量</td>
                            <td width=100 align=center>小計</td>
                        </tr> {% endif %} <div class='listgroup'>
                            <div class='listgroup-item'>
                                <tr>
                                    <td>{{ item.name }}</td>
                                    <td align=right>{{ item.price }}</td>
                                    <td align=center>{{ item.quantity }}</td>
                                    <td align=right>{{ item.subtotal }}</td>
                                </tr>
                            </div>
                        </div> {% if forloop.last %} </table> {% endif %} {% empty %} <em>此訂單是空的</em> {% endfor %} {{ paypal_form.render }} </div>
                <div class='panel panel-footer'> NT$:{{ total }}元 </div>
            </div>
        </div>
    </div>
</div> {% endblock %}

 

付款完成頁面。

<!-- payment_done.html (mshop project) --> {% extends "base.html" %} {% block title %}Pay using PayPal{% endblock %} {% block content %} <div class='container'> {% for message in messages %} <div class='alert alert-{{message.tags}}'>{{ message }}</div> {% endfor %} <div class='row'>
        <div class='col-md-12'>
            <div class='panel panel-default'>
                <div class='panel-heading' align=center>
                    <h3>歡迎光臨迷你小電商</h3> {% if user.socialaccount_set.all.0.extra_data.name %} {{user.socialaccount_set.all.0.extra_data.name}}<br/>
                            <img src='{{user.socialaccount_set.all.0.get_avatar_url}}' width='100'> {% else %} Welcome: {{ user.username }} {% endif %} </div>
            </div>
        </div>
    </div>
    <div class='row'>
        <div class='col-sm-12'>
            <div class='panel panel-info'>
                <div class='panel panel-heading'>
                    <h4>從PayPal付款成功</h4>
                </div>
                <div class='panel panel-body'> 感謝您的支持,我們會盡快處理您的訂單。 </div>
                <div class='panel panel-footer'>
                </div>
            </div>
        </div>
    </div>
</div> {% endblock %}

 

取消付款頁面。

<!-- payment_canceled.html (mshop project) --> {% extends "base.html" %} {% block title %}PayPal 付款取消通知{% endblock %} {% block content %} <div class='container'> {% for message in messages %} <div class='alert alert-{{message.tags}}'>{{ message }}</div> {% endfor %} <div class='row'>
        <div class='col-md-12'>
            <div class='panel panel-default'>
                <div class='panel-heading' align=center>
                    <h3>歡迎光臨迷你小電商</h3> {% if user.socialaccount_set.all.0.extra_data.name %} {{user.socialaccount_set.all.0.extra_data.name}}<br/>
                            <img src='{{user.socialaccount_set.all.0.get_avatar_url}}' width='100'> {% else %} Welcome: {{ user.username }} {% endif %} </div>
            </div>
        </div>
    </div>
    <div class='row'>
        <div class='col-sm-12'>
            <div class='panel panel-info'>
                <div class='panel panel-heading'>
                    <h4>您剛剛取消了PayPal的付款</h4>
                </div>
                <div class='panel panel-body'>
                    <p>請再次檢查您的付款,或是返回<a href='/myorders/'>我的訂單</a>選用其它付款方式。</p>
                </div>
                <div class='panel panel-footer'>
                </div>
            </div>
        </div>
    </div>
</div> {% endblock %}

 

PayPal 在處理完在線付款流程后會另外發送一個 HTTP 數據給我們的網站,我們應該編寫一個處理這個信號的函數,更改我們數據庫中的內容,為了確保我們設置的監聽函數可以被系統加載且保持運行,在 views.py 的同級目錄中建立一個名為 signal.py 文件。

from mysite import models from paypal.standard.models import ST_PP_COMPLETED from paypal.standard.ipn.signals import valid_ipn_received def payment_notfication(sender, **kwargs): ipn_obj = sender if ipn_obj.payment_status == ST_PP_COMPLETED: order_id = ipn_obj.invocie.split('-')[-1] order = models.Order.objects.get(id = order_id) order_id.paid = True order.save() valid_ipn_received.connect(payment_notfication)

 

在同一文件夾下再創建一個名為 apps.py 的文件,確保上述編寫的函數在一開始的時候就能夠加載。

from django.apps import AppConfig class PaymentConfig(AppConfig): name = 'mysite' verbose_name = 'Mysite'

    def ready(self): import mysite.signal

 

在同一文件夾下的 __init__.py 中加入以下語句,確保我們在應用程序初始化加載的時候,可以把我們自定義的應用程序環境設置成能夠加載自定義的工作。

default_app_config = 'mysite.apps.PaymentConfig'

 

通過上述設置,我們的網站已經可以正確地接受訂單並使用 PayPal 付款了,我們可以在 PayPal 開發者網站(https://developer.paypal.com/)申請一個測試賬號來進行付款測試。

 

點擊進入 dashboard 界面,點擊 sandbox 下的 account 選項,我們可以在此創建一個測試賬號。

 

點擊創建賬號下的 profile 選項,進入詳情頁,設置此賬號的密碼,並將 Payment Review 的功能設置為 Off。

 

接下來我們便可以在我們的網站中使用這個測試賬號付款了,點擊前往付款,調用 payment 函數,加載含有正確數據的付款按鈕,點擊后便跳轉到 paypal 的沙盒付款頁面,我們在其中填入我們之前建立好的測試賬號信息,登錄后便可以付款了。

 

付款成功后便返回我們之前編寫好的付款成功頁面。

 

注意:中國大陸的 paypal 賬號不能用來測試實際支付,需要大陸以外的 paypal 賬戶才可測試實際支付。(真是坑。。。)

不然付款的時候會出現下列界面。

 

到這里,我們的付款便已經成功了,但是 PayPal 無法將支付狀態通知發送到我們的應用,這是由於我們的項目運行在外部無法訪問的 127.0.0.1 上。我們使用 Ngrok 來實現因特網訪問開發環境。

 

 在 Ngrok 官網 https://ngrok.com/ 下載解壓文件並關聯賬號后,運行下列命令。

./ngrok http 8000

 

這個命令將在 8000 端口為本地主機創建一個通道並為其設置一個網絡可以訪問的主機名稱,得到以下輸出:

我們可以通過訪問 Forwarding 中的網址來連接我們構建在本地的網站。

然后付款后便能在自己本地網站的后台管理看到 paypal ipn 的信息,我這里顯示的狀態是 pending,按理來說應該是 completed ,可能 paypal 設置中需要更改,這樣的話需要將 signal.py 中 ST_PP_COMPLETED 修改為 ST_PP_PENDING,這樣 signal.py 便能正常處理 paypal 返回的信息,將訂單狀態更改為已完成。

 

至此,我們便完成了調用 paypal 實現第三方網站支付的功能。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM