Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How/where to stop Django Formwizards 'done' method being called on payment failure

I've looked through the django documentation, done lots of googling and have tried quite a few different solutions but to no avail.

I've created a 3 part form using Django's FormWizard. Once the last form (a payment form) is validated, I send a payment request to a payment gateway.

I'm doing the payment processing in the 'process_step' method of the FormWizard.

I'm having difficulty figuring out how to have the FormWizard show the payment page again when the payment fails. As it is now, the FormWizard's 'done' method gets called (after I've done my processing in process_step), as all of the forms have been validated.

I'm wondering if I need to override the call method. Not really sure how to do that, but I'm currently trying to figure that out.

Any help would be much appreciated. Regards, Shawn

class TrainingWizard(FormWizard):

def process_step(self,request,form,step):
    if step == 0:
        self.extra_context = {'stepOne': "One"}
    if step == 1:
        self.extra_context = {'stepTwo': "Two"}
    if step == 2:
        if self.get_response != "Success":
            #Payment Failed
            #Add error message
            #Show Payment Form Again to allow user to retry     
    return

def get_response(self):
    #return "Success"
    return "Declined"

def done(self, request, form_list):
    return HttpResponseRedirect('/training-registration-complete/')
like image 858
shawn Avatar asked Aug 11 '10 20:08

shawn


1 Answers

I finally hit upon a solution. I'm using the SessionWizardView class in Django 1.4.

I overrode the render_done() function (copied it directly out of django/django/contrib/formtools/wizard/views.py and tweaked it.

Between 'final_form_list.append(form_obj)' and the last three lines (starting with 'done_response = self.done(final_form_list, **kwargs)') I handle talking with the payment gateway.

If there's an error, I redirect back to the last step in the form (using self.render.revalidation_failure()) and they can try again.

Here's a snippet of code:

try:
    charge = stripe.Charge.create(
        amount=price_in_cents,
        currency="usd",
        card=token,
        description="BlahStore Order Number: %s" %(self.order.pk),
    )
except (stripe.APIConnectionError, stripe.APIError, stripe.AuthenticationError, stripe.CardError, stripe.InvalidRequestError, stripe.StripeError) as e:
    from django.forms import forms
    from django.forms.util import ErrorList
    errors = final_form_list[3]._errors.setdefault(forms.NON_FIELD_ERRORS, ErrorList())
    errors.append(e.message)
    return self.render_revalidation_failure(3, final_form_list[3], **kwargs)

You'll notice I have '3' hard-coded in a few places--this is because my wizard has four steps and the array is zero based. I set the error message to display and then redirect. I'm sure it would be fairly easy to adapt this if you wanted to stick with process_step(), but I personally think TheRightWay is to override the render_done() function.

like image 164
Aaron C. de Bruyn Avatar answered Sep 21 '22 19:09

Aaron C. de Bruyn