Search code examples
djangopaypaldjango-viewsdjango-paypal

Understanding where to put the IPN reciever function in django-paypal


I have a similar issue to the post here in regards to setting up the receiver function for the paypal-ipn

What we are trying to do(I don't know this other person but I assume we stand together on this topic) is to understand how to deduce a path to receiving a paypal IPN signal in which we then can update the django database.

I have implemented the django-paypal API by following the directions here

Overall what I do is I create a view in my views.py as follows

def payment_page(request):
   """This fucntion returns payment page for the form"""
   if not request.session.get('form-submitted', False):
       return HttpResponseRedirect(reverse('grap_main:error_page'))
   else:
       amount = set_payment()
       paypal_dict = {
           "business": "business@gmail.com",
           "amount": str(amount),
           "item_name": "2017 USGF Championships",
           "notify_url": "https://15b6b6cb.ngrok.io" + reverse('paypal-ipn'),
           "return_url": "https://15b6b6cb.ngrok.io/Confirm",
           "cancel_return": "https://15b6b6cb.ngrok.io/RegistrationForm",

  }
       form = PayPalPaymentsForm(initial=paypal_dict)
       context = confirm_information()
       context["amount"] = amount
       context["form"] = form
       request.session['form-submitted'] = False
       valid_ipn_received.connect(show_me_the_money)
       return render(request, "grap_main/payment.html", context)

Where my then I have payment.html which then create the paypal button simply by using the line as advised in the documentation

{{ form.render }}

Now I can receive a POST to the paypal url that I specified in the documentation however I don't know where I should put my signal function that will grab the IPNs once someone has completed a purchase.

def show_me_the_money(sender, **kwargs):
   """signal function"""
   ipn_obj = sender
   if ipn_obj.payment_status == ST_PP_COMPLETED:
       print 'working'
   else:
       print "not working"

Right now I am calling this function in the view payment_page() however I know this is before the POST to the paypal and is not correct I don't understand where I should call the show_me_the_money() function. I am used to creating a views in which is called from the html script as shown below

def register(request):
   """aquire information from new entry"""
   if request.method != 'POST':
       form = RegisterForm()
   else:
       if 'refill' in request.POST:
           form = RegisterForm()
       else:
           form = RegisterForm(data=request.POST)
           if form.is_valid():
               form.save()
               request.session['form-submitted'] = True
               return HttpResponseRedirect(reverse('grap_main:payment_page'))

.html

<form action="{% url 'grap_main:register' %}" method='post' class="form">
     {% csrf_token %}
     {% bootstrap_form form %}
     <br>
     {% buttons %}
     <center>
       <button name='submit' class="btn btn-primary">Submit</button>
     </center>
     {% endbuttons %}
   </form>

I believe I need to call the function after a person has completed a purchase however I don't know how to target that time window in my code. I want to make sure I handle a case in which the user once there are done paying don't always return to the merchant site.

Any help on this subject would not only benefit me but also the earlier poster. I hope to make a tutorial when I figure this out to help others that might get stuck as well.

Please note that I have also use ngrok to make sure my project is accessible to the paypal IPN service. I am using two urls.py files as well, i which the main one looks as so

urlpatterns = [
   url(r'^paypal/', include('paypal.standard.ipn.urls')),
   url(r'^admin/', admin.site.urls),
   url(r'', include('grap_main.urls', namespace='grap_main')),
]

where the 'grap_main.urls' are all the specific views for my site, ex.

urlpatterns = [
   url(r'^$', views.index, name='index'),
   url(r'^Confirm', views.confirm, name='confirm'),
   url(r'^Events', views.events, name='events'),
   .........]

Solution

  • [UPDATE]: Forgot to mention where and how to put the code that you wrote (the handler if you like). Inside the handlers.py file write it like this:

    # grap_main/signals/handlers.py
    
    from paypal.standard.ipn.signals import valid_ipn_received, invalid_ipn_received
    
    @receiver(valid_ipn_received)
    def show_me_the_money(sender, **kwargs):
        """Do things here upon a valid IPN message received"""
        ...
    
    @receiver(invalid_ipn_received)
    def do_not_show_me_the_money(sender, **kwargs):
        """Do things here upon an invalid IPN message received"""
        ...
    

    Although signals (and handlers) can live anywhere, a nice convention is to store them inside your app's signals dir. Thus, the structure of your grap_main app should look like this:

    project/
        grap_main/
            apps.py
            models.py
            views.py
            ...
            migrations/
            signals/
                __init__.py
                signals.py
                handlers.py            
    

    Now, in order for the handlers to be loaded, write (or add) this inside grap_main/apps.py

    # apps.py
    
    from django.apps.config import AppConfig
    
    
    class GrapMainConfig(AppConfig):
        name = 'grapmain'
        verbose_name = 'grap main' # this name will display in the Admin. You may translate this value if you want using ugettex_lazy
    
        def ready(self):
            import grap_main.signals.handlers
    

    Finally, in your settings.py file, under the INSTALLED_APPS setting, instead of 'grap_main' use this:

    # settings.py
    
    INSTALLED_APPS = [
        ... # other apps here
        'grap_main.apps.GrapMainConfig',
        ... # other apps here
    ]
    

    Some side notes:

    1. Instead of using standard forms to render the paypal button use encrypted buttons. That way you will prevent any potential modification of the form (mostly change of prices).

    2. I was exactly in your track a few months ago using the terrific django-paypal package. However, I needed to upgrade my project to Python 3. But because I used the encrypted buttons I was unable to upgrade. Why? Because encrypted buttons depend on M2Crypto which doesn't support (yet) Python 3. What did I so? I dropped django-paypal and joined Braintree which is a PayPal company. Now, not only I can accept PayPal payments but also credit card ones. All Python 3!