Django集成PayPal实现第三方支付功能完整教程

季宁
季宁
发布时间:2026-10-13 | 4人浏览过
原创

本文详解如何在Django项目中集成PayPal实现第三方支付功能。涵盖django-paypal模块安装、settings配置、URL路由设置及视图逻辑编写。重点讲解PayPal沙盒环境搭建、测试账号创建、Ngrok内网穿透处理IPN通知,以及信号监听与订单状态更新机制,帮助开发者快速完成支付功能落地。

实现思路:通过调用第三方支付API接口来完成支付功能。起初考虑使用支付宝,但发现其官方未提供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配置,分别对应付款完成通知、账务处理、显示付款完成页面以及取消付款操作。

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实现第三方网站支付的功能。

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持本站。

相关标签:

本站声明

本文内容仅供参考,不代表本站观点。转载或引用请注明出处。如发现内容问题,请通过联系我们页面反馈。

Python 相关产品和应用

全部下载

专题列表