Django: Reverse for 'verify-payment' with arguments '('',)' not found. 1 pattern(s) tried: ['course/u/(?P<ref>[^/]+)/$']

wrote a view to verify payment bbut when i hit the payment button i get this error that say Reverse for 'verify-payment' with arguments '('',)' not found. 1 pattern(s) tried: ['course/u/(?P<ref>[^/]+)/

i know it’s a url configuation error, i am not passing in the right arguments but i dont know what to pass in, i already tried payment.ref

views.py

@login_required
def course_checkout(request, slug):
    course = Course.objects.get(slug=slug)
    user = request.user
    action = request.GET.get('action')
    order = None 
    payment = None 
    error = None 

    try:
        user_course = UserCourse.objects.get(user=user, course=course)
        error = "Congratulations! You are Already Enrolled In This Course"
    except:
        pass 
        amount = None 
        if error is None:
            amount = int((course.price - (course.price * course.discount * 0.01)) * 100)
        if amount == 0:
            userCourse = UserCourse(user=user, course=course)
            userCourse.save()
            return redirect('course:course-content', course.slug) # change this to redirect to my courses page with a button to start course because this course is free

        if action == 'create_payment':
            currency = "NGN"
            notes = {
            "email": user.email,
            "name": user.username
            }
            reciept = f"DexxaEd-{int(time())}"
            order = client.order.create(
                {
                'reciept' : reciept,
                'notes' : notes,
                'amount' : amount,
                'currency' : currency,
                'paystack_public_key' : settings.PAYSTACK_PUBLIC_KEY,
                }
            )

            payment = Payment()
            payment.user = user 
            payment.course = course 
            payment.order_id = order.get('id')
            payment.save()


    context = {
        "course":course,
        "order":order,
        "payment":payment,
        "user":user,
        "error":error,
    }

    return render(request, "course/course-checkout.html", context)


def verify_payment(request: HttpRequest, ref: str) -> HttpResponse:
    payment = get_object_or_404(Payment, ref=ref)
    verified = payment.verify_payment()
    return redirect("userauths:profile")

models.py

class Payment(models.Model):
    order_id = models.CharField(max_length = 50 , null = False)
    payment_id = models.CharField(max_length = 50)
    user_course = models.ForeignKey(UserCourse , null = True , blank = True ,  on_delete=models.CASCADE)
    user = models.ForeignKey(User ,  on_delete=models.CASCADE)
    course = models.ForeignKey(Course , on_delete=models.CASCADE)
    date = models.DateTimeField(auto_now_add=True)
    status = models.BooleanField(default=False)

    email = models.EmailField()
    ref = models.CharField(max_length=200)
    verified = models.BooleanField(default=False)

    def __str__(self):
        return f"Payment: {self.course.price}"
    
    def save(self ,*args, **kwargs):
        while not self.ref:
            ref = secrets.token_urlsafe(50)
            objects_with_similar_ref = Payment.objects.filter(ref=ref)
            if not objects_with_similar_ref:
                self.ref = ref 
            super().save(*args, **kwargs)
    
    def amount_value(self) -> int:
        return self.course.price * 100

    def verify_payment(self):
        paystack = PayStack()
        status, result = paystack,verify_payment(self.ref, self.course.price)
        if status:
            if result['course.price'] / 100 == self.course.price:
            # if result['amount'] / 100 == self.course.price:
                self.verified = True
            self.save()
        if self.verified:
            return True 
        return False

urls.py

    path('u/<str:ref>/', views.verify_payment, name = 'verify-payment'),

course-checkout.html

{{ course.price }}
<button id="django-paystack-button" onclick="payWithPayStack()" class="chck-btn22">Make Payment</button>

<script>
    function payWithPaystack() {
        let currency = "USD";
        let plan = "";
        let ref = "{{payment.ref}}";
        let obj = {
            key: "{{ paystack_public_key }}",
            email: "{{ request.user.email }}",
            amount: "{{course.price_value}}",
            ref: ref,
            callback: function (response) {
                window.location.href = "{% url 'course:verify-payment' payment.ref %}";
            },
        };

        if (Boolean(currency)) {
            obj.currency = currency.toUpperCase();
        }
        if (Boolean(plan)) {
            obj.plan = plan;
        }
        var handler = PaystackPop.setup(obj);
        handler.openIframe();
    }
</script>

Did you check in admin or shell that the ref is getting saved? Looks like the url got an empty ref?

Here is my attempt.

Added get_absolute_url to the end of the model:

class Payment(models.Model):
    order_id = models.CharField(max_length = 50 , null = False)
    payment_id = models.CharField(max_length = 50)
    user_course = models.ForeignKey(UserCourse , null = True , blank = True ,  on_delete=models.CASCADE)
    user = models.ForeignKey(User ,  on_delete=models.CASCADE)
    course = models.ForeignKey(Course , on_delete=models.CASCADE)
    date = models.DateTimeField(auto_now_add=True)
    status = models.BooleanField(default=False)

    email = models.EmailField()
    ref = models.CharField(max_length=200)
    verified = models.BooleanField(default=False)

    def __str__(self):
        return f"Payment: {self.course.price}"
    
    def save(self ,*args, **kwargs):
        while not self.ref:
            ref = secrets.token_urlsafe(50)
            objects_with_similar_ref = Payment.objects.filter(ref=ref)
            if not objects_with_similar_ref:
                self.ref = ref 
            super().save(*args, **kwargs)
    
    def amount_value(self) -> int:
        return self.course.price * 100

    def verify_payment(self):
        paystack = PayStack()
        status, result = paystack,verify_payment(self.ref, self.course.price)
        if status:
            if result['course.price'] / 100 == self.course.price:
            # if result['amount'] / 100 == self.course.price:
                self.verified = True
            self.save()
        if self.verified:
            return True 
        return False

    def get_absolute_url(self):
        return reverse("course:verify-payment", kwargs={
            "ref": self.ref,
        })

Then in your script in course-checkout.html, change to:

            callback: function (response) {
                window.location.href = "{{ payment.get_absolute_url }}";
            },

Note I didn’t test this.

1 Like

try this?

{% url 'course:verify-payment' ref=payment.ref %}

instead of

{% url 'course:verify-payment' payment.ref %}
1 Like

thanks for your response, i tried it and i got this new error message Reverse for 'verify-payment' with keyword arguments '{'ref': ''}' not found. 1 pattern(s) tried: ['course/u/(?P<ref>[^/]+)/$']

thanks for your answer, i tried it and it returned the same error, but i noticed something, when i manually try adding a payment instance from the admin shell, it shows added successfully but when i refresh it gets deleted with this message Payment with ID “None” doesn’t exist. Perhaps it was deleted?

what does you (relevant) urls.py file look like?

this is what it all looks like

from django.urls import path
from course import views

app_name = 'course'

urlpatterns = [
    path('categories/<slug:category_slug>', views.course_lists_by_categories, name="course-list-categories"),
    path('<slug:course_slug>', views.course_details, name="course-detail"),
    path('<slug:course_slug>/contents/', views.CourseContent, name="course-content"),
    path('categories/', views.course_categories, name="course-categories"),
    path('u/cart', views.cart, name="cart"),
    path('u/checkout', views.checkout, name="checkout"),
    path('u/update-item/', views.update_item, name="update-item"),
    path('u/process-order/', views.processOrder, name="process-order"),
    path('u/check-out/<str:slug>', views.course_checkout, name = 'check-out'),
    path('u/<str:ref>/', views.verify_payment, name = 'verify-payment'),


]

Am I being silly if I do not see something like self.ref = reference anywhere in self.verify_payment? In other words, is there anywhere in your code where you actually save the ref to the database (or even to the in-memory object)?

Inside the save function there is self.ref = ref. Is this function working and being called? It seems it’s not saving the ref?

I haven’t seen this structure before, is it right to save model changes from within the model? What if you move the save logic to the view does it work, like:


payment = Payment()
payment.user = user 
payment.course = course 
payment.order_id = order.get('id')
while not payment.ref:
	ref = secrets.token_urlsafe(50)
	objects_with_similar_ref = Payment.objects.filter(ref=ref)
	if not objects_with_similar_ref:
		payment.ref = ref 
payment.save()

Just a side note - if the URL is only defined with one parameter named ref, then these two forms are identical. There’s no technical reason to prefer one over the other.

Yes. You can do pretty much anything in a Model.save method. See Overriding predefined model methods

1 Like

What that error message is telling you specifically, is that the variable payment.ref is either undefined, an empty string, or None. (Can’t tell which, it all works out the same.)

You need to verify in your view that the payment object being set in the context has an appropriate value.

Your overridden save method looks ok to me, but you probably want to check your database to verify that that field has been set.

It could also be that payment itself is undefined, which is possible given how the logic of your view is constructed.

If a person is already enrolled, or the action is not create_payment, then that block of code does not get executed. You then fall through to creating the context with payment = None (from up at the top), and then the rendering of that template fails.