Django集成PayPal支付无法接收IPN响应及相关实现问题
Django集成PayPal IPN问题解决指南
核心问题解答
1. IPN无需自行编写视图
你使用的django-paypal库已经封装好了IPN监听、验证的全套逻辑,只需要在项目路由中注册库自带的IPN路由即可:
# 项目主urls.py urlpatterns = [ # 你的其他路由 path('paypal/ipn/', include('paypal.standard.ipn.urls')), ]
注册完成后reverse('paypal-ipn')就能正常解析,你代码中注释的notify_url配置可以直接启用。
2. 表单存放位置说明
你当前把PayPalPaymentsForm放在PaymentProcess视图中的写法完全正确:
- 这个表单的作用是生成跳转到PayPal支付页的提交入口,不需要额外存放在其他位置
- 你注释的
return_url、cancel_return对应两个自定义视图,需要你自行编写,分别处理用户支付成功后跳转、支付取消后跳转的逻辑,对应路由命名为payment_done、payment_canceled即可
3. 信号生效配置步骤
你写的信号逻辑本身没有问题,只要做两处配置保证Django启动时能加载到信号即可:
- 首先在你存放
signals.py的应用的apps.py中添加信号导入逻辑:
from django.apps import AppConfig class 你的应用名Config(AppConfig): default_auto_field = 'django.db.models.BigAutoField' name = '你的应用名' def ready(self): import 你的应用名.signals
- 检查
settings.py的INSTALLED_APPS中已经注册了paypal.standard.ipn和你自己的业务应用
修正后可直接使用的代码示例
修正后的PaymentProcess视图
class PaymentProcess(View): def get(self,request): host = request.get_host() payment = PaymentRecords.objects.get( order_id = request.session['order_id'] ) paypal_dict = { 'business': settings.PAYPAL_RECEIVER_EMAIL, 'amount': payment.total_amount, 'item_name': 'Item_Name_xyz', 'invoice': f'INV{payment.order_id}{str(randint(10,5000))}', # 用订单号保证唯一 'currency_code': 'USD', 'custom': payment.order_id, # 传递订单号,IPN回调时用于关联订单 '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 , 'restprac/index.html',{'form':form})
修正后的signals.py
from paypal.standard.ipn.signals import valid_ipn_received from paypal.standard.models import ST_PP_COMPLETED from django.conf import settings from .models import PaymentRecords def show_me_the_money(sender, **kwargs): ipn_obj = sender if ipn_obj.payment_status == ST_PP_COMPLETED: # 校验收款邮箱防止篡改 if ipn_obj.receiver_email != settings.PAYPAL_RECEIVER_EMAIL: return # 关联业务订单 try: payment = PaymentRecords.objects.get(order_id=ipn_obj.custom) except PaymentRecords.DoesNotExist: return # 校验金额和币种 if ipn_obj.mc_gross == payment.total_amount and ipn_obj.mc_currency == 'USD': # 支付校验通过,修改订单状态、执行业务逻辑 payment.is_paid = True payment.paypal_txn_id = ipn_obj.txn_id payment.save() # 其他后续逻辑:发通知、解锁服务等 valid_ipn_received.connect(show_me_the_money)
注意事项
- 本地测试时需要用内网穿透工具把本地端口映射到公网,PayPal才能正常发送IPN回调到本地环境
- 生产环境的
notify_url必须使用HTTPS协议,PayPal强制要求生产回调地址走HTTPS invoice字段必须全局唯一,重复的发票号会导致PayPal拒绝返回回调- 不要在
return_url对应的视图中做支付状态变更,该地址是用户前端跳转地址,存在伪造风险,所有支付状态变更逻辑必须放在IPN信号中处理
内容的提问来源于stack exchange,提问作者Atif Shafi
相关产品推荐
相关产品推荐

