Search code examples
djangopaypaldjango-rest-frameworkpaypal-sandboxpayment

Forbidden (CSRF cookie not set.): /paypal/ | Django


I am having problems with django-paypal, the problem occurs at the moment of receiving the paypal IPN when the payment is successfully completed.

Error:

Forbidden (CSRF cookie not set.): /paypal/
[15/Sep/2019 22:53:04] "POST /paypal/ HTTP/1.1" 403 2896
Forbidden (CSRF cookie not set.): /paypal/
[15/Sep/2019 22:53:19] "POST /paypal/ HTTP/1.1" 403 2896
Forbidden (CSRF cookie not set.): /paypal/
[15/Sep/2019 22:53:42] "POST /paypal/ HTTP/1.1" 403 2896
Forbidden (CSRF cookie not set.): /paypal/
[15/Sep/2019 22:54:24] "POST /paypal/ HTTP/1.1" 403 2896
Forbidden (CSRF cookie not set.): /paypal/
[15/Sep/2019 22:55:45] "POST /paypal/ HTTP/1.1" 403 2896

I don't know what is happening, I started to investigate the error but I did not achieve much, investigating I put in the file settings.py CSRF_COOKIE_SECURE as True, and even then it did not work, any solution ?.


This is the code that makes the payment:

def process_payment(request, pk):
    course = get_object_or_404(Course, pk = pk)
    host = request.get_host()

    paypal_dict = {
        'business': settings.PAYPAL_RECEIVER_EMAIL,
        'item_name': course.title,
        'amount': course.price,
        'currency_code': 'USD',
        'notify_url': 'http://{}{}'.format(host, reverse('paypal-ipn')),
        'return_url': 'http://{}{}'.format(host, reverse('course:list')),
        'cancel_return': 'http://{}{}'.format(host, reverse('payment_cancelled')),
    }

    form = PayPalPaymentsForm(initial = paypal_dict)
    return render(request, 'carts/process_payment.html', {'form': form, 'course': course})

UPDATE

The view that gives error was not made by me, is from django-paypal, which is responsible for managing the paypal IPN, the view is called ipn. The view is as follows:

https://github.com/spookylukey/django-paypal/blob/master/paypal/standard/ipn/views.py

That view is the notify_url.

For more information, this is the guide I am doing: https://overiq.com/django-paypal-integration-with-django-paypal/

These are the urls.py of my application:

urlpatterns = [
    path('cart/', CartDetailView.as_view(), name = 'cart'),
    path('cart-add/', views.add_course, name = 'add_course'),
    path('process-payment/<int:pk>/', views.process_payment, name='process_payment'),
    path('payment-cancelled/', views.payment_canceled, name='payment_cancelled'),

]

These are the urls.py of the django-paypal ipn application

urlpatterns = [
    url(r'^$', views.ipn, name="paypal-ipn"),
]

This would be my urlconf:

urlpatterns = [
    path('django-admin/', admin.site.urls),

    # Paths of My Apps
    path('admin/', include(administration_patterns)),
    path('', include('core.urls')),
    path('', include('carts.urls')),
    path('', include(course_patterns)),
    path('paypal/', include('paypal.standard.ipn.urls')),

    # Paths of Auth
    path('accounts/', include('django.contrib.auth.urls')),
    path('accounts/', include('registration.urls')),
]

Solution

  • Try moving the Paypal URL pattern to the top to ensure that the request is actually routed to django-paypal:

    urlpatterns = [
        path('django-admin/', admin.site.urls),
    
        # Paths of My Apps
        path('paypal/', include('paypal.standard.ipn.urls')),
        path('admin/', include(administration_patterns)),
        path('', include('core.urls')),
        path('', include('carts.urls')),
        path('', include(course_patterns)),
    
        # Paths of Auth
        path('accounts/', include('django.contrib.auth.urls')),
        path('accounts/', include('registration.urls')),
    ]
    

    Why are you seeing this error?

    The request is then routed to a view, which is not csrf_exempt, so the CSRF middleware expects a CSRF cookie.

    As the request is sent from the Paypal server, it does not contain a CSRF cookie, so you are getting the error Forbidden (CSRF cookie not set.)

    Why is the request routed to the wrong view?

    Patterns are matched in a depth-first approach, meaning Django first tries all patterns included from 'core.urls', 'carts.urls' and course_patterns before trying to match path('paypal/', include('paypal.standard.ipn.urls'))

    One of the included URL patterns matches the path /paypal/, maybe something like path('<slug>/', course_view) in course_patterns.

    Moving path('paypal/', include('paypal.standard.ipn.urls')), up in the urlpatterns list ensures that Django tries to match it first.